aboutsummaryrefslogtreecommitdiff
path: root/Marlin/src/gcode
diff options
context:
space:
mode:
authorGeorgiy Bondarenko <69736697+nehilo@users.noreply.github.com>2021-03-04 20:54:23 +0300
committerGeorgiy Bondarenko <69736697+nehilo@users.noreply.github.com>2021-03-04 20:54:23 +0300
commite8701195e66f2d27ffe17fb514eae8173795aaf7 (patch)
tree9f519c4abf6556b9ae7190a6210d87ead1dfadde /Marlin/src/gcode
downloadkp3s-lgvl-e8701195e66f2d27ffe17fb514eae8173795aaf7.tar.xz
kp3s-lgvl-e8701195e66f2d27ffe17fb514eae8173795aaf7.zip
Initial commit
Diffstat (limited to 'Marlin/src/gcode')
-rw-r--r--Marlin/src/gcode/bedlevel/G26.cpp872
-rw-r--r--Marlin/src/gcode/bedlevel/G35.cpp167
-rw-r--r--Marlin/src/gcode/bedlevel/G42.cpp73
-rw-r--r--Marlin/src/gcode/bedlevel/M420.cpp245
-rw-r--r--Marlin/src/gcode/bedlevel/abl/G29.cpp901
-rw-r--r--Marlin/src/gcode/bedlevel/abl/M421.cpp74
-rw-r--r--Marlin/src/gcode/bedlevel/mbl/G29.cpp193
-rw-r--r--Marlin/src/gcode/bedlevel/mbl/M421.cpp59
-rw-r--r--Marlin/src/gcode/bedlevel/ubl/G29.cpp36
-rw-r--r--Marlin/src/gcode/bedlevel/ubl/M421.cpp70
-rw-r--r--Marlin/src/gcode/calibrate/G28.cpp493
-rw-r--r--Marlin/src/gcode/calibrate/G33.cpp648
-rw-r--r--Marlin/src/gcode/calibrate/G34.cpp157
-rw-r--r--Marlin/src/gcode/calibrate/G34_M422.cpp533
-rw-r--r--Marlin/src/gcode/calibrate/G425.cpp623
-rw-r--r--Marlin/src/gcode/calibrate/G76_M192_M871.cpp369
-rw-r--r--Marlin/src/gcode/calibrate/M100.cpp379
-rw-r--r--Marlin/src/gcode/calibrate/M12.cpp39
-rw-r--r--Marlin/src/gcode/calibrate/M425.cpp111
-rw-r--r--Marlin/src/gcode/calibrate/M48.cpp275
-rw-r--r--Marlin/src/gcode/calibrate/M665.cpp112
-rw-r--r--Marlin/src/gcode/calibrate/M666.cpp105
-rw-r--r--Marlin/src/gcode/calibrate/M852.cpp106
-rw-r--r--Marlin/src/gcode/config/M200-M205.cpp191
-rw-r--r--Marlin/src/gcode/config/M217.cpp171
-rw-r--r--Marlin/src/gcode/config/M218.cpp71
-rw-r--r--Marlin/src/gcode/config/M220.cpp51
-rw-r--r--Marlin/src/gcode/config/M221.cpp47
-rw-r--r--Marlin/src/gcode/config/M281.cpp68
-rw-r--r--Marlin/src/gcode/config/M301.cpp91
-rw-r--r--Marlin/src/gcode/config/M302.cpp63
-rw-r--r--Marlin/src/gcode/config/M304.cpp48
-rw-r--r--Marlin/src/gcode/config/M305.cpp81
-rw-r--r--Marlin/src/gcode/config/M43.cpp386
-rw-r--r--Marlin/src/gcode/config/M540.cpp40
-rw-r--r--Marlin/src/gcode/config/M575.cpp75
-rw-r--r--Marlin/src/gcode/config/M672.cpp98
-rw-r--r--Marlin/src/gcode/config/M92.cpp114
-rw-r--r--Marlin/src/gcode/control/M108_M112_M410.cpp56
-rw-r--r--Marlin/src/gcode/control/M111.cpp77
-rw-r--r--Marlin/src/gcode/control/M120_M121.cpp34
-rw-r--r--Marlin/src/gcode/control/M17_M18_M84.cpp69
-rw-r--r--Marlin/src/gcode/control/M211.cpp46
-rw-r--r--Marlin/src/gcode/control/M226.cpp60
-rw-r--r--Marlin/src/gcode/control/M280.cpp55
-rw-r--r--Marlin/src/gcode/control/M3-M5.cpp140
-rw-r--r--Marlin/src/gcode/control/M350_M351.cpp64
-rw-r--r--Marlin/src/gcode/control/M380_M381.cpp56
-rw-r--r--Marlin/src/gcode/control/M400.cpp33
-rw-r--r--Marlin/src/gcode/control/M42.cpp107
-rw-r--r--Marlin/src/gcode/control/M605.cpp185
-rw-r--r--Marlin/src/gcode/control/M7-M9.cpp63
-rw-r--r--Marlin/src/gcode/control/M80_M81.cpp113
-rw-r--r--Marlin/src/gcode/control/M85.cpp35
-rw-r--r--Marlin/src/gcode/control/M993_M994.cpp88
-rw-r--r--Marlin/src/gcode/control/M997.cpp36
-rw-r--r--Marlin/src/gcode/control/M999.cpp45
-rw-r--r--Marlin/src/gcode/control/T.cpp70
-rw-r--r--Marlin/src/gcode/eeprom/M500-M504.cpp104
-rw-r--r--Marlin/src/gcode/feature/L6470/M122.cpp151
-rw-r--r--Marlin/src/gcode/feature/L6470/M906.cpp370
-rw-r--r--Marlin/src/gcode/feature/L6470/M916-918.cpp651
-rw-r--r--Marlin/src/gcode/feature/advance/M900.cpp147
-rw-r--r--Marlin/src/gcode/feature/baricuda/M126-M129.cpp58
-rw-r--r--Marlin/src/gcode/feature/camera/M240.cpp204
-rw-r--r--Marlin/src/gcode/feature/cancel/M486.cpp57
-rw-r--r--Marlin/src/gcode/feature/caselight/M355.cpp73
-rw-r--r--Marlin/src/gcode/feature/clean/G12.cpp80
-rw-r--r--Marlin/src/gcode/feature/controllerfan/M710.cpp81
-rw-r--r--Marlin/src/gcode/feature/digipot/M907-M910.cpp102
-rw-r--r--Marlin/src/gcode/feature/filwidth/M404-M407.cpp71
-rw-r--r--Marlin/src/gcode/feature/fwretract/G10_G11.cpp51
-rw-r--r--Marlin/src/gcode/feature/fwretract/M207-M209.cpp74
-rw-r--r--Marlin/src/gcode/feature/i2c/M260_M261.cpp76
-rw-r--r--Marlin/src/gcode/feature/leds/M150.cpp84
-rw-r--r--Marlin/src/gcode/feature/leds/M7219.cpp93
-rw-r--r--Marlin/src/gcode/feature/macro/M810-M819.cpp65
-rw-r--r--Marlin/src/gcode/feature/mixing/M163-M165.cpp101
-rw-r--r--Marlin/src/gcode/feature/mixing/M166.cpp103
-rw-r--r--Marlin/src/gcode/feature/network/M552-M554.cpp126
-rw-r--r--Marlin/src/gcode/feature/password/M510-M512.cpp83
-rw-r--r--Marlin/src/gcode/feature/pause/G27.cpp41
-rw-r--r--Marlin/src/gcode/feature/pause/G60.cpp58
-rw-r--r--Marlin/src/gcode/feature/pause/G61.cpp73
-rw-r--r--Marlin/src/gcode/feature/pause/M125.cpp90
-rw-r--r--Marlin/src/gcode/feature/pause/M600.cpp172
-rw-r--r--Marlin/src/gcode/feature/pause/M603.cpp65
-rw-r--r--Marlin/src/gcode/feature/pause/M701_M702.cpp235
-rw-r--r--Marlin/src/gcode/feature/power_monitor/M430.cpp70
-rw-r--r--Marlin/src/gcode/feature/powerloss/M1000.cpp89
-rw-r--r--Marlin/src/gcode/feature/powerloss/M413.cpp62
-rw-r--r--Marlin/src/gcode/feature/prusa_MMU2/M403.cpp49
-rw-r--r--Marlin/src/gcode/feature/runout/M412.cpp64
-rw-r--r--Marlin/src/gcode/feature/trinamic/M122.cpp60
-rw-r--r--Marlin/src/gcode/feature/trinamic/M569.cpp186
-rw-r--r--Marlin/src/gcode/feature/trinamic/M906.cpp173
-rw-r--r--Marlin/src/gcode/feature/trinamic/M911-M914.cpp429
-rw-r--r--Marlin/src/gcode/gcode.cpp1084
-rw-r--r--Marlin/src/gcode/gcode.h906
-rw-r--r--Marlin/src/gcode/gcode_d.cpp181
-rw-r--r--Marlin/src/gcode/geometry/G17-G19.cpp53
-rw-r--r--Marlin/src/gcode/geometry/G53-G59.cpp101
-rw-r--r--Marlin/src/gcode/geometry/G92.cpp105
-rw-r--r--Marlin/src/gcode/geometry/M206_M428.cpp94
-rw-r--r--Marlin/src/gcode/host/M110.cpp34
-rw-r--r--Marlin/src/gcode/host/M113.cpp45
-rw-r--r--Marlin/src/gcode/host/M114.cpp214
-rw-r--r--Marlin/src/gcode/host/M115.cpp171
-rw-r--r--Marlin/src/gcode/host/M118.cpp66
-rw-r--r--Marlin/src/gcode/host/M119.cpp33
-rw-r--r--Marlin/src/gcode/host/M16.cpp40
-rw-r--r--Marlin/src/gcode/host/M360.cpp186
-rw-r--r--Marlin/src/gcode/host/M876.cpp39
-rw-r--r--Marlin/src/gcode/lcd/M0_M1.cpp87
-rw-r--r--Marlin/src/gcode/lcd/M117.cpp36
-rw-r--r--Marlin/src/gcode/lcd/M145.cpp59
-rw-r--r--Marlin/src/gcode/lcd/M250.cpp38
-rw-r--r--Marlin/src/gcode/lcd/M300.cpp45
-rw-r--r--Marlin/src/gcode/lcd/M414.cpp44
-rw-r--r--Marlin/src/gcode/lcd/M73.cpp48
-rw-r--r--Marlin/src/gcode/lcd/M995.cpp48
-rw-r--r--Marlin/src/gcode/motion/G0_G1.cpp121
-rw-r--r--Marlin/src/gcode/motion/G2_G3.cpp370
-rw-r--r--Marlin/src/gcode/motion/G4.cpp44
-rw-r--r--Marlin/src/gcode/motion/G5.cpp65
-rw-r--r--Marlin/src/gcode/motion/G6.cpp61
-rw-r--r--Marlin/src/gcode/motion/G80.cpp38
-rw-r--r--Marlin/src/gcode/motion/M290.cpp136
-rw-r--r--Marlin/src/gcode/parser.cpp406
-rw-r--r--Marlin/src/gcode/parser.h441
-rw-r--r--Marlin/src/gcode/probe/G30.cpp66
-rw-r--r--Marlin/src/gcode/probe/G31_G32.cpp40
-rw-r--r--Marlin/src/gcode/probe/G38.cpp133
-rw-r--r--Marlin/src/gcode/probe/M401_M402.cpp49
-rw-r--r--Marlin/src/gcode/probe/M851.cpp97
-rw-r--r--Marlin/src/gcode/probe/M951.cpp71
-rw-r--r--Marlin/src/gcode/queue.cpp699
-rw-r--r--Marlin/src/gcode/queue.h187
-rw-r--r--Marlin/src/gcode/scara/M360-M364.cpp81
-rw-r--r--Marlin/src/gcode/sd/M1001.cpp111
-rw-r--r--Marlin/src/gcode/sd/M20.cpp43
-rw-r--r--Marlin/src/gcode/sd/M21_M22.cpp44
-rw-r--r--Marlin/src/gcode/sd/M23.cpp44
-rw-r--r--Marlin/src/gcode/sd/M24_M25.cpp115
-rw-r--r--Marlin/src/gcode/sd/M26.cpp38
-rw-r--r--Marlin/src/gcode/sd/M27.cpp52
-rw-r--r--Marlin/src/gcode/sd/M28_M29.cpp72
-rw-r--r--Marlin/src/gcode/sd/M30.cpp40
-rw-r--r--Marlin/src/gcode/sd/M32.cpp59
-rw-r--r--Marlin/src/gcode/sd/M33.cpp48
-rw-r--r--Marlin/src/gcode/sd/M34.cpp42
-rw-r--r--Marlin/src/gcode/sd/M524.cpp42
-rw-r--r--Marlin/src/gcode/sd/M808.cpp51
-rw-r--r--Marlin/src/gcode/sd/M928.cpp39
-rw-r--r--Marlin/src/gcode/stats/M31.cpp40
-rw-r--r--Marlin/src/gcode/stats/M75-M78.cpp73
-rw-r--r--Marlin/src/gcode/temp/M104_M109.cpp200
-rw-r--r--Marlin/src/gcode/temp/M105.cpp51
-rw-r--r--Marlin/src/gcode/temp/M106_M107.cpp95
-rw-r--r--Marlin/src/gcode/temp/M140_M190.cpp138
-rw-r--r--Marlin/src/gcode/temp/M141_M191.cpp89
-rw-r--r--Marlin/src/gcode/temp/M155.cpp40
-rw-r--r--Marlin/src/gcode/temp/M303.cpp85
-rw-r--r--Marlin/src/gcode/units/G20_G21.cpp39
-rw-r--r--Marlin/src/gcode/units/M149.cpp38
-rw-r--r--Marlin/src/gcode/units/M82_M83.cpp33
166 files changed, 23211 insertions, 0 deletions
diff --git a/Marlin/src/gcode/bedlevel/G26.cpp b/Marlin/src/gcode/bedlevel/G26.cpp
new file mode 100644
index 0000000..5a79aaa
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/G26.cpp
@@ -0,0 +1,872 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * G26 Mesh Validation Tool
+ *
+ * G26 is a Mesh Validation Tool intended to provide support for the Marlin Unified Bed Leveling System.
+ * In order to fully utilize and benefit from the Marlin Unified Bed Leveling System an accurate Mesh must
+ * be defined. G29 is designed to allow the user to quickly validate the correctness of her Mesh. It will
+ * first heat the bed and nozzle. It will then print lines and circles along the Mesh Cell boundaries and
+ * the intersections of those lines (respectively).
+ *
+ * This action allows the user to immediately see where the Mesh is properly defined and where it needs to
+ * be edited. The command will generate the Mesh lines closest to the nozzle's starting position. Alternatively
+ * the user can specify the X and Y position of interest with command parameters. This allows the user to
+ * focus on a particular area of the Mesh where attention is needed.
+ *
+ * B # Bed Set the Bed Temperature. If not specified, a default of 60 C. will be assumed.
+ *
+ * C Current When searching for Mesh Intersection points to draw, use the current nozzle location
+ * as the base for any distance comparison.
+ *
+ * D Disable Disable the Unified Bed Leveling System. In the normal case the user is invoking this
+ * command to see how well a Mesh as been adjusted to match a print surface. In order to do
+ * this the Unified Bed Leveling System is turned on by the G26 command. The D parameter
+ * alters the command's normal behavior and disables the Unified Bed Leveling System even if
+ * it is on.
+ *
+ * H # Hotend Set the Nozzle Temperature. If not specified, a default of 205 C. will be assumed.
+ *
+ * I # Preset Heat the Nozzle and Bed based on a Material Preset (if material presets are defined).
+ *
+ * F # Filament Used to specify the diameter of the filament being used. If not specified
+ * 1.75mm filament is assumed. If you are not getting acceptable results by using the
+ * 'correct' numbers, you can scale this number up or down a little bit to change the amount
+ * of filament that is being extruded during the printing of the various lines on the bed.
+ *
+ * K Keep-On Keep the heaters turned on at the end of the command.
+ *
+ * L # Layer Layer height. (Height of nozzle above bed) If not specified .20mm will be used.
+ *
+ * O # Ooooze How much your nozzle will Ooooze filament while getting in position to print. This
+ * is over kill, but using this parameter will let you get the very first 'circle' perfect
+ * so you have a trophy to peel off of the bed and hang up to show how perfectly you have your
+ * Mesh calibrated. If not specified, a filament length of .3mm is assumed.
+ *
+ * P # Prime Prime the nozzle with specified length of filament. If this parameter is not
+ * given, no prime action will take place. If the parameter specifies an amount, that much
+ * will be purged before continuing. If no amount is specified the command will start
+ * purging filament until the user provides an LCD Click and then it will continue with
+ * printing the Mesh. You can carefully remove the spent filament with a needle nose
+ * pliers while holding the LCD Click wheel in a depressed state. If you do not have
+ * an LCD, you must specify a value if you use P.
+ *
+ * Q # Multiplier Retraction Multiplier. Normally not needed. Retraction defaults to 1.0mm and
+ * un-retraction is at 1.2mm These numbers will be scaled by the specified amount
+ *
+ * R # Repeat Prints the number of patterns given as a parameter, starting at the current location.
+ * If a parameter isn't given, every point will be printed unless G26 is interrupted.
+ * This works the same way that the UBL G29 P4 R parameter works.
+ *
+ * NOTE: If you do not have an LCD, you -must- specify R. This is to ensure that you are
+ * aware that there's some risk associated with printing without the ability to abort in
+ * cases where mesh point Z value may be inaccurate. As above, if you do not include a
+ * parameter, every point will be printed.
+ *
+ * S # Nozzle Used to control the size of nozzle diameter. If not specified, a .4mm nozzle is assumed.
+ *
+ * U # Random Randomize the order that the circles are drawn on the bed. The search for the closest
+ * un-drawn circle is still done. But the distance to the location for each circle has a
+ * random number of the specified size added to it. Specifying S50 will give an interesting
+ * deviation from the normal behavior on a 10 x 10 Mesh.
+ *
+ * X # X Coord. Specify the starting location of the drawing activity.
+ *
+ * Y # Y Coord. Specify the starting location of the drawing activity.
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(G26_MESH_VALIDATION)
+
+#define G26_OK false
+#define G26_ERR true
+
+#include "../../gcode/gcode.h"
+#include "../../feature/bedlevel/bedlevel.h"
+
+#include "../../MarlinCore.h"
+#include "../../module/planner.h"
+#include "../../module/stepper.h"
+#include "../../module/motion.h"
+#include "../../module/tool_change.h"
+#include "../../module/temperature.h"
+#include "../../lcd/marlinui.h"
+
+#define EXTRUSION_MULTIPLIER 1.0
+#define PRIME_LENGTH 10.0
+#define OOZE_AMOUNT 0.3
+
+#define INTERSECTION_CIRCLE_RADIUS 5
+#define CROSSHAIRS_SIZE 3
+
+#ifndef G26_RETRACT_MULTIPLIER
+ #define G26_RETRACT_MULTIPLIER 1.0 // x 1mm
+#endif
+
+#ifndef G26_XY_FEEDRATE
+ #define G26_XY_FEEDRATE (PLANNER_XY_FEEDRATE() / 3.0)
+#endif
+
+#ifndef G26_XY_FEEDRATE_TRAVEL
+ #define G26_XY_FEEDRATE_TRAVEL (PLANNER_XY_FEEDRATE() / 1.5)
+#endif
+
+#if CROSSHAIRS_SIZE >= INTERSECTION_CIRCLE_RADIUS
+ #error "CROSSHAIRS_SIZE must be less than INTERSECTION_CIRCLE_RADIUS."
+#endif
+
+#define G26_OK false
+#define G26_ERR true
+
+#if ENABLED(ARC_SUPPORT)
+ void plan_arc(const xyze_pos_t&, const ab_float_t&, const bool, const uint8_t);
+#endif
+
+constexpr float g26_e_axis_feedrate = 0.025;
+
+static MeshFlags circle_flags, horizontal_mesh_line_flags, vertical_mesh_line_flags;
+float g26_random_deviation = 0.0;
+
+static bool g26_retracted = false; // Track the retracted state of the nozzle so mismatched
+ // retracts/recovers won't result in a bad state.
+
+float g26_extrusion_multiplier,
+ g26_retraction_multiplier,
+ g26_layer_height,
+ g26_prime_length;
+
+xy_pos_t g26_xy_pos; // = { 0, 0 }
+
+int16_t g26_bed_temp,
+ g26_hotend_temp;
+
+int8_t g26_prime_flag;
+
+#if HAS_LCD_MENU
+
+ /**
+ * If the LCD is clicked, cancel, wait for release, return true
+ */
+ bool user_canceled() {
+ if (!ui.button_pressed()) return false; // Return if the button isn't pressed
+ ui.set_status_P(GET_TEXT(MSG_G26_CANCELED), 99);
+ TERN_(HAS_LCD_MENU, ui.quick_feedback());
+ ui.wait_for_release();
+ return true;
+ }
+
+#endif
+
+mesh_index_pair find_closest_circle_to_print(const xy_pos_t &pos) {
+ float closest = 99999.99;
+ mesh_index_pair out_point;
+
+ out_point.pos = -1;
+
+ GRID_LOOP(i, j) {
+ if (!circle_flags.marked(i, j)) {
+ // We found a circle that needs to be printed
+ const xy_pos_t m = { _GET_MESH_X(i), _GET_MESH_Y(j) };
+
+ // Get the distance to this intersection
+ float f = (pos - m).magnitude();
+
+ // It is possible that we are being called with the values
+ // to let us find the closest circle to the start position.
+ // But if this is not the case, add a small weighting to the
+ // distance calculation to help it choose a better place to continue.
+ f += (g26_xy_pos - m).magnitude() / 15.0f;
+
+ // Add the specified amount of Random Noise to our search
+ if (g26_random_deviation > 1.0) f += random(0.0, g26_random_deviation);
+
+ if (f < closest) {
+ closest = f; // Found a closer un-printed location
+ out_point.pos.set(i, j); // Save its data
+ out_point.distance = closest;
+ }
+ }
+ }
+ circle_flags.mark(out_point); // Mark this location as done.
+ return out_point;
+}
+
+void move_to(const float &rx, const float &ry, const float &z, const float &e_delta) {
+ static float last_z = -999.99;
+
+ const xy_pos_t dest = { rx, ry };
+
+ const bool has_xy_component = dest != current_position; // Check if X or Y is involved in the movement.
+ const bool has_e_component = e_delta != 0.0;
+
+ destination = current_position;
+
+ if (z != last_z) {
+ last_z = destination.z = z;
+ const feedRate_t fr_mm_s = planner.settings.max_feedrate_mm_s[Z_AXIS] * 0.5f; // Use half of the Z_AXIS max feed rate
+ prepare_internal_move_to_destination(fr_mm_s);
+ }
+
+ // If X or Y in combination with E is involved do a 'normal' move.
+ // If X or Y with no E is involved do a 'fast' move
+ // Otherwise retract/recover/hop.
+ destination = dest;
+ destination.e += e_delta;
+ const feedRate_t fr_mm_s = has_xy_component
+ ? (has_e_component ? feedRate_t(G26_XY_FEEDRATE) : feedRate_t(G26_XY_FEEDRATE_TRAVEL))
+ : planner.settings.max_feedrate_mm_s[E_AXIS] * 0.666f;
+ prepare_internal_move_to_destination(fr_mm_s);
+}
+
+FORCE_INLINE void move_to(const xyz_pos_t &where, const float &de) { move_to(where.x, where.y, where.z, de); }
+
+void retract_filament(const xyz_pos_t &where) {
+ if (!g26_retracted) { // Only retract if we are not already retracted!
+ g26_retracted = true;
+ move_to(where, -1.0f * g26_retraction_multiplier);
+ }
+}
+
+// TODO: Parameterize the Z lift with a define
+void retract_lift_move(const xyz_pos_t &s) {
+ retract_filament(destination);
+ move_to(current_position.x, current_position.y, current_position.z + 0.5f, 0.0); // Z lift to minimize scraping
+ move_to(s.x, s.y, s.z + 0.5f, 0.0); // Get to the starting point with no extrusion while lifted
+}
+
+void recover_filament(const xyz_pos_t &where) {
+ if (g26_retracted) { // Only un-retract if we are retracted.
+ move_to(where, 1.2f * g26_retraction_multiplier);
+ g26_retracted = false;
+ }
+}
+
+/**
+ * print_line_from_here_to_there() takes two cartesian coordinates and draws a line from one
+ * to the other. But there are really three sets of coordinates involved. The first coordinate
+ * is the present location of the nozzle. We don't necessarily want to print from this location.
+ * We first need to move the nozzle to the start of line segment where we want to print. Once
+ * there, we can use the two coordinates supplied to draw the line.
+ *
+ * Note: Although we assume the first set of coordinates is the start of the line and the second
+ * set of coordinates is the end of the line, it does not always work out that way. This function
+ * optimizes the movement to minimize the travel distance before it can start printing. This saves
+ * a lot of time and eliminates a lot of nonsensical movement of the nozzle. However, it does
+ * cause a lot of very little short retracement of th nozzle when it draws the very first line
+ * segment of a 'circle'. The time this requires is very short and is easily saved by the other
+ * cases where the optimization comes into play.
+ */
+void print_line_from_here_to_there(const xyz_pos_t &s, const xyz_pos_t &e) {
+
+ // Distances to the start / end of the line
+ xy_float_t svec = current_position - s, evec = current_position - e;
+
+ const float dist_start = HYPOT2(svec.x, svec.y),
+ dist_end = HYPOT2(evec.x, evec.y),
+ line_length = HYPOT(e.x - s.x, e.y - s.y);
+
+ // If the end point of the line is closer to the nozzle, flip the direction,
+ // moving from the end to the start. On very small lines the optimization isn't worth it.
+ if (dist_end < dist_start && (INTERSECTION_CIRCLE_RADIUS) < ABS(line_length))
+ return print_line_from_here_to_there(e, s);
+
+ // Decide whether to retract & lift
+ if (dist_start > 2.0) retract_lift_move(s);
+
+ move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift
+
+ const float e_pos_delta = line_length * g26_e_axis_feedrate * g26_extrusion_multiplier;
+
+ recover_filament(destination);
+ move_to(e, e_pos_delta); // Get to the ending point with an appropriate amount of extrusion
+}
+
+inline bool look_for_lines_to_connect() {
+ xyz_pos_t s, e;
+ s.z = e.z = g26_layer_height;
+
+ GRID_LOOP(i, j) {
+
+ if (TERN0(HAS_LCD_MENU, user_canceled())) return true;
+
+ if (i < (GRID_MAX_POINTS_X)) { // Can't connect to anything farther to the right than GRID_MAX_POINTS_X.
+ // Already a half circle at the edge of the bed.
+
+ if (circle_flags.marked(i, j) && circle_flags.marked(i + 1, j)) { // Test whether a leftward line can be done
+ if (!horizontal_mesh_line_flags.marked(i, j)) {
+ // Two circles need a horizontal line to connect them
+ s.x = _GET_MESH_X( i ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // right edge
+ e.x = _GET_MESH_X(i + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // left edge
+
+ LIMIT(s.x, X_MIN_POS + 1, X_MAX_POS - 1);
+ s.y = e.y = constrain(_GET_MESH_Y(j), Y_MIN_POS + 1, Y_MAX_POS - 1);
+ LIMIT(e.x, X_MIN_POS + 1, X_MAX_POS - 1);
+
+ if (position_is_reachable(s.x, s.y) && position_is_reachable(e.x, e.y))
+ print_line_from_here_to_there(s, e);
+
+ horizontal_mesh_line_flags.mark(i, j); // Mark done, even if skipped
+ }
+ }
+
+ if (j < (GRID_MAX_POINTS_Y)) { // Can't connect to anything further back than GRID_MAX_POINTS_Y.
+ // Already a half circle at the edge of the bed.
+
+ if (circle_flags.marked(i, j) && circle_flags.marked(i, j + 1)) { // Test whether a downward line can be done
+ if (!vertical_mesh_line_flags.marked(i, j)) {
+ // Two circles that need a vertical line to connect them
+ s.y = _GET_MESH_Y( j ) + (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // top edge
+ e.y = _GET_MESH_Y(j + 1) - (INTERSECTION_CIRCLE_RADIUS - (CROSSHAIRS_SIZE)); // bottom edge
+
+ s.x = e.x = constrain(_GET_MESH_X(i), X_MIN_POS + 1, X_MAX_POS - 1);
+ LIMIT(s.y, Y_MIN_POS + 1, Y_MAX_POS - 1);
+ LIMIT(e.y, Y_MIN_POS + 1, Y_MAX_POS - 1);
+
+ if (position_is_reachable(s.x, s.y) && position_is_reachable(e.x, e.y))
+ print_line_from_here_to_there(s, e);
+
+ vertical_mesh_line_flags.mark(i, j); // Mark done, even if skipped
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Turn on the bed and nozzle heat and
+ * wait for them to get up to temperature.
+ */
+inline bool turn_on_heaters() {
+
+ SERIAL_ECHOLNPGM("Waiting for heatup.");
+
+ #if HAS_HEATED_BED
+
+ if (g26_bed_temp > 25) {
+ #if HAS_WIRED_LCD
+ ui.set_status_P(GET_TEXT(MSG_G26_HEATING_BED), 99);
+ ui.quick_feedback();
+ TERN_(HAS_LCD_MENU, ui.capture());
+ #endif
+ thermalManager.setTargetBed(g26_bed_temp);
+
+ // Wait for the temperature to stabilize
+ if (!thermalManager.wait_for_bed(true
+ #if G26_CLICK_CAN_CANCEL
+ , true
+ #endif
+ )
+ ) return G26_ERR;
+ }
+
+ #endif // HAS_HEATED_BED
+
+ // Start heating the active nozzle
+ #if HAS_WIRED_LCD
+ ui.set_status_P(GET_TEXT(MSG_G26_HEATING_NOZZLE), 99);
+ ui.quick_feedback();
+ #endif
+ thermalManager.setTargetHotend(g26_hotend_temp, active_extruder);
+
+ // Wait for the temperature to stabilize
+ if (!thermalManager.wait_for_hotend(active_extruder, true
+ #if G26_CLICK_CAN_CANCEL
+ , true
+ #endif
+ )) return G26_ERR;
+
+ #if HAS_WIRED_LCD
+ ui.reset_status();
+ ui.quick_feedback();
+ #endif
+
+ return G26_OK;
+}
+
+/**
+ * Prime the nozzle if needed. Return true on error.
+ */
+inline bool prime_nozzle() {
+
+ const feedRate_t fr_slow_e = planner.settings.max_feedrate_mm_s[E_AXIS] / 15.0f;
+ #if HAS_LCD_MENU && !HAS_TOUCH_BUTTONS // ui.button_pressed issue with touchscreen
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ float Total_Prime = 0.0;
+ #endif
+
+ if (g26_prime_flag == -1) { // The user wants to control how much filament gets purged
+ ui.capture();
+ ui.set_status_P(GET_TEXT(MSG_G26_MANUAL_PRIME), 99);
+ ui.chirp();
+
+ destination = current_position;
+
+ recover_filament(destination); // Make sure G26 doesn't think the filament is retracted().
+
+ while (!ui.button_pressed()) {
+ ui.chirp();
+ destination.e += 0.25;
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ Total_Prime += 0.25;
+ if (Total_Prime >= EXTRUDE_MAXLENGTH) {
+ ui.release();
+ return G26_ERR;
+ }
+ #endif
+ prepare_internal_move_to_destination(fr_slow_e);
+ destination = current_position;
+ planner.synchronize(); // Without this synchronize, the purge is more consistent,
+ // but because the planner has a buffer, we won't be able
+ // to stop as quickly. So we put up with the less smooth
+ // action to give the user a more responsive 'Stop'.
+ }
+
+ ui.wait_for_release();
+
+ ui.set_status_P(GET_TEXT(MSG_G26_PRIME_DONE), 99);
+ ui.quick_feedback();
+ ui.release();
+ }
+ else
+ #endif
+ {
+ #if HAS_WIRED_LCD
+ ui.set_status_P(GET_TEXT(MSG_G26_FIXED_LENGTH), 99);
+ ui.quick_feedback();
+ #endif
+ destination = current_position;
+ destination.e += g26_prime_length;
+ prepare_internal_move_to_destination(fr_slow_e);
+ destination.e -= g26_prime_length;
+ retract_filament(destination);
+ }
+
+ return G26_OK;
+}
+
+/**
+ * G26: Mesh Validation Pattern generation.
+ *
+ * Used to interactively edit the mesh by placing the
+ * nozzle in a problem area and doing a G29 P4 R command.
+ *
+ * Parameters:
+ *
+ * B Bed Temperature
+ * C Continue from the Closest mesh point
+ * D Disable leveling before starting
+ * F Filament diameter
+ * H Hotend Temperature
+ * K Keep heaters on when completed
+ * L Layer Height
+ * O Ooze extrusion length
+ * P Prime length
+ * Q Retraction multiplier
+ * R Repetitions (number of grid points)
+ * S Nozzle Size (diameter) in mm
+ * T Tool index to change to, if included
+ * U Random deviation (50 if no value given)
+ * X X position
+ * Y Y position
+ */
+void GcodeSuite::G26() {
+ SERIAL_ECHOLNPGM("G26 starting...");
+
+ // Don't allow Mesh Validation without homing first,
+ // or if the parameter parsing did not go OK, abort
+ if (homing_needed_error()) return;
+
+ // Change the tool first, if specified
+ if (parser.seenval('T')) tool_change(parser.value_int());
+
+ g26_extrusion_multiplier = EXTRUSION_MULTIPLIER;
+ g26_retraction_multiplier = G26_RETRACT_MULTIPLIER;
+ g26_layer_height = MESH_TEST_LAYER_HEIGHT;
+ g26_prime_length = PRIME_LENGTH;
+ g26_bed_temp = MESH_TEST_BED_TEMP;
+ g26_hotend_temp = MESH_TEST_HOTEND_TEMP;
+ g26_prime_flag = 0;
+
+ float g26_nozzle = MESH_TEST_NOZZLE_SIZE,
+ g26_filament_diameter = DEFAULT_NOMINAL_FILAMENT_DIA,
+ g26_ooze_amount = parser.linearval('O', OOZE_AMOUNT);
+
+ bool g26_continue_with_closest = parser.boolval('C'),
+ g26_keep_heaters_on = parser.boolval('K');
+
+ // Accept 'I' if temperature presets are defined
+ #if PREHEAT_COUNT
+ const uint8_t preset_index = parser.seenval('I') ? _MIN(parser.value_byte(), PREHEAT_COUNT - 1) + 1 : 0;
+ #endif
+
+ #if HAS_HEATED_BED
+
+ // Get a temperature from 'I' or 'B'
+ int16_t bedtemp = 0;
+
+ // Use the 'I' index if temperature presets are defined
+ #if PREHEAT_COUNT
+ if (preset_index) bedtemp = ui.material_preset[preset_index - 1].bed_temp;
+ #endif
+
+ // Look for 'B' Bed Temperature
+ if (parser.seenval('B')) bedtemp = parser.value_celsius();
+
+ if (bedtemp) {
+ if (!WITHIN(bedtemp, 40, BED_MAX_TARGET)) {
+ SERIAL_ECHOLNPAIR("?Specified bed temperature not plausible (40-", int(BED_MAX_TARGET), "C).");
+ return;
+ }
+ g26_bed_temp = bedtemp;
+ }
+
+ #endif // HAS_HEATED_BED
+
+ if (parser.seenval('L')) {
+ g26_layer_height = parser.value_linear_units();
+ if (!WITHIN(g26_layer_height, 0.0, 2.0)) {
+ SERIAL_ECHOLNPGM("?Specified layer height not plausible.");
+ return;
+ }
+ }
+
+ if (parser.seen('Q')) {
+ if (parser.has_value()) {
+ g26_retraction_multiplier = parser.value_float();
+ if (!WITHIN(g26_retraction_multiplier, 0.05, 15.0)) {
+ SERIAL_ECHOLNPGM("?Specified Retraction Multiplier not plausible.");
+ return;
+ }
+ }
+ else {
+ SERIAL_ECHOLNPGM("?Retraction Multiplier must be specified.");
+ return;
+ }
+ }
+
+ if (parser.seenval('S')) {
+ g26_nozzle = parser.value_float();
+ if (!WITHIN(g26_nozzle, 0.1, 2.0)) {
+ SERIAL_ECHOLNPGM("?Specified nozzle size not plausible.");
+ return;
+ }
+ }
+
+ if (parser.seen('P')) {
+ if (!parser.has_value()) {
+ #if HAS_LCD_MENU
+ g26_prime_flag = -1;
+ #else
+ SERIAL_ECHOLNPGM("?Prime length must be specified when not using an LCD.");
+ return;
+ #endif
+ }
+ else {
+ g26_prime_flag++;
+ g26_prime_length = parser.value_linear_units();
+ if (!WITHIN(g26_prime_length, 0.0, 25.0)) {
+ SERIAL_ECHOLNPGM("?Specified prime length not plausible.");
+ return;
+ }
+ }
+ }
+
+ if (parser.seenval('F')) {
+ g26_filament_diameter = parser.value_linear_units();
+ if (!WITHIN(g26_filament_diameter, 1.0, 4.0)) {
+ SERIAL_ECHOLNPGM("?Specified filament size not plausible.");
+ return;
+ }
+ }
+ g26_extrusion_multiplier *= sq(1.75) / sq(g26_filament_diameter); // If we aren't using 1.75mm filament, we need to
+ // scale up or down the length needed to get the
+ // same volume of filament
+
+ g26_extrusion_multiplier *= g26_filament_diameter * sq(g26_nozzle) / sq(0.3); // Scale up by nozzle size
+
+ // Get a temperature from 'I' or 'H'
+ int16_t noztemp = 0;
+
+ // Accept 'I' if temperature presets are defined
+ #if PREHEAT_COUNT
+ if (preset_index) noztemp = ui.material_preset[preset_index - 1].hotend_temp;
+ #endif
+
+ // Look for 'H' Hotend Temperature
+ if (parser.seenval('H')) noztemp = parser.value_celsius();
+
+ // If any preset or temperature was specified
+ if (noztemp) {
+ if (!WITHIN(noztemp, 165, (HEATER_0_MAXTEMP) - (HOTEND_OVERSHOOT))) {
+ SERIAL_ECHOLNPGM("?Specified nozzle temperature not plausible.");
+ return;
+ }
+ g26_hotend_temp = noztemp;
+ }
+
+ // 'U' to Randomize and optionally set circle deviation
+ if (parser.seen('U')) {
+ randomSeed(millis());
+ // This setting will persist for the next G26
+ g26_random_deviation = parser.has_value() ? parser.value_float() : 50.0;
+ }
+
+ // Get repeat from 'R', otherwise do one full circuit
+ int16_t g26_repeats;
+ #if HAS_LCD_MENU
+ g26_repeats = parser.intval('R', GRID_MAX_POINTS + 1);
+ #else
+ if (!parser.seen('R')) {
+ SERIAL_ECHOLNPGM("?(R)epeat must be specified when not using an LCD.");
+ return;
+ }
+ else
+ g26_repeats = parser.has_value() ? parser.value_int() : GRID_MAX_POINTS + 1;
+ #endif
+ if (g26_repeats < 1) {
+ SERIAL_ECHOLNPGM("?(R)epeat value not plausible; must be at least 1.");
+ return;
+ }
+
+ // Set a position with 'X' and/or 'Y'. Default: current_position
+ g26_xy_pos.set(parser.seenval('X') ? RAW_X_POSITION(parser.value_linear_units()) : current_position.x,
+ parser.seenval('Y') ? RAW_Y_POSITION(parser.value_linear_units()) : current_position.y);
+ if (!position_is_reachable(g26_xy_pos)) {
+ SERIAL_ECHOLNPGM("?Specified X,Y coordinate out of bounds.");
+ return;
+ }
+
+ /**
+ * Wait until all parameters are verified before altering the state!
+ */
+ set_bed_leveling_enabled(!parser.seen('D'));
+
+ do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES);
+
+ #if DISABLED(NO_VOLUMETRICS)
+ bool volumetric_was_enabled = parser.volumetric_enabled;
+ parser.volumetric_enabled = false;
+ planner.calculate_volumetric_multipliers();
+ #endif
+
+ if (turn_on_heaters() != G26_OK) goto LEAVE;
+
+ current_position.e = 0.0;
+ sync_plan_position_e();
+
+ if (g26_prime_flag && prime_nozzle() != G26_OK) goto LEAVE;
+
+ /**
+ * Bed is preheated
+ *
+ * Nozzle is at temperature
+ *
+ * Filament is primed!
+ *
+ * It's "Show Time" !!!
+ */
+
+ circle_flags.reset();
+ horizontal_mesh_line_flags.reset();
+ vertical_mesh_line_flags.reset();
+
+ // Move nozzle to the specified height for the first layer
+ destination = current_position;
+ destination.z = g26_layer_height;
+ move_to(destination, 0.0);
+ move_to(destination, g26_ooze_amount);
+
+ TERN_(HAS_LCD_MENU, ui.capture());
+
+ #if DISABLED(ARC_SUPPORT)
+
+ /**
+ * Pre-generate radius offset values at 30 degree intervals to reduce CPU load.
+ */
+ #define A_INT 30
+ #define _ANGS (360 / A_INT)
+ #define A_CNT (_ANGS / 2)
+ #define _IND(A) ((A + _ANGS * 8) % _ANGS)
+ #define _COS(A) (trig_table[_IND(A) % A_CNT] * (_IND(A) >= A_CNT ? -1 : 1))
+ #define _SIN(A) (-_COS((A + A_CNT / 2) % _ANGS))
+ #if A_CNT & 1
+ #error "A_CNT must be a positive value. Please change A_INT."
+ #endif
+ float trig_table[A_CNT];
+ LOOP_L_N(i, A_CNT)
+ trig_table[i] = INTERSECTION_CIRCLE_RADIUS * cos(RADIANS(i * A_INT));
+
+ #endif // !ARC_SUPPORT
+
+ mesh_index_pair location;
+ do {
+ // Find the nearest confluence
+ location = find_closest_circle_to_print(g26_continue_with_closest ? xy_pos_t(current_position) : g26_xy_pos);
+
+ if (location.valid()) {
+ const xy_pos_t circle = _GET_MESH_POS(location.pos);
+
+ // If this mesh location is outside the printable radius, skip it.
+ if (!position_is_reachable(circle)) continue;
+
+ // Determine where to start and end the circle,
+ // which is always drawn counter-clockwise.
+ const xy_int8_t st = location;
+ const bool f = st.y == 0,
+ r = st.x >= GRID_MAX_POINTS_X - 1,
+ b = st.y >= GRID_MAX_POINTS_Y - 1;
+
+ #if ENABLED(ARC_SUPPORT)
+
+ #define ARC_LENGTH(quarters) (INTERSECTION_CIRCLE_RADIUS * M_PI * (quarters) / 2)
+ #define INTERSECTION_CIRCLE_DIAM ((INTERSECTION_CIRCLE_RADIUS) * 2)
+
+ xy_float_t e = { circle.x + INTERSECTION_CIRCLE_RADIUS, circle.y };
+ xyz_float_t s = e;
+
+ // Figure out where to start and end the arc - we always print counterclockwise
+ float arc_length = ARC_LENGTH(4);
+ if (st.x == 0) { // left edge
+ if (!f) { s.x = circle.x; s.y -= INTERSECTION_CIRCLE_RADIUS; }
+ if (!b) { e.x = circle.x; e.y += INTERSECTION_CIRCLE_RADIUS; }
+ arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2);
+ }
+ else if (r) { // right edge
+ if (b) s.set(circle.x - (INTERSECTION_CIRCLE_RADIUS), circle.y);
+ else s.set(circle.x, circle.y + INTERSECTION_CIRCLE_RADIUS);
+ if (f) e.set(circle.x - (INTERSECTION_CIRCLE_RADIUS), circle.y);
+ else e.set(circle.x, circle.y - (INTERSECTION_CIRCLE_RADIUS));
+ arc_length = (f || b) ? ARC_LENGTH(1) : ARC_LENGTH(2);
+ }
+ else if (f) {
+ e.x -= INTERSECTION_CIRCLE_DIAM;
+ arc_length = ARC_LENGTH(2);
+ }
+ else if (b) {
+ s.x -= INTERSECTION_CIRCLE_DIAM;
+ arc_length = ARC_LENGTH(2);
+ }
+
+ const ab_float_t arc_offset = circle - s;
+ const xy_float_t dist = current_position - s; // Distance from the start of the actual circle
+ const float dist_start = HYPOT2(dist.x, dist.y);
+ const xyze_pos_t endpoint = {
+ e.x, e.y, g26_layer_height,
+ current_position.e + (arc_length * g26_e_axis_feedrate * g26_extrusion_multiplier)
+ };
+
+ if (dist_start > 2.0) {
+ s.z = g26_layer_height + 0.5f;
+ retract_lift_move(s);
+ }
+
+ s.z = g26_layer_height;
+ move_to(s, 0.0); // Get to the starting point with no extrusion / un-Z lift
+
+ recover_filament(destination);
+
+ const feedRate_t old_feedrate = feedrate_mm_s;
+ feedrate_mm_s = PLANNER_XY_FEEDRATE() * 0.1f;
+ plan_arc(endpoint, arc_offset, false, 0); // Draw a counter-clockwise arc
+ feedrate_mm_s = old_feedrate;
+ destination = current_position;
+
+ if (TERN0(HAS_LCD_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation
+
+ #else // !ARC_SUPPORT
+
+ int8_t start_ind = -2, end_ind = 9; // Assume a full circle (from 5:00 to 5:00)
+ if (st.x == 0) { // Left edge? Just right half.
+ start_ind = f ? 0 : -3; // 03:00 to 12:00 for front-left
+ end_ind = b ? 0 : 2; // 06:00 to 03:00 for back-left
+ }
+ else if (r) { // Right edge? Just left half.
+ start_ind = b ? 6 : 3; // 12:00 to 09:00 for front-right
+ end_ind = f ? 5 : 8; // 09:00 to 06:00 for back-right
+ }
+ else if (f) { // Front edge? Just back half.
+ start_ind = 0; // 03:00
+ end_ind = 5; // 09:00
+ }
+ else if (b) { // Back edge? Just front half.
+ start_ind = 6; // 09:00
+ end_ind = 11; // 03:00
+ }
+
+ for (int8_t ind = start_ind; ind <= end_ind; ind++) {
+
+ if (TERN0(HAS_LCD_MENU, user_canceled())) goto LEAVE; // Check if the user wants to stop the Mesh Validation
+
+ xyz_float_t p = { circle.x + _COS(ind ), circle.y + _SIN(ind ), g26_layer_height },
+ q = { circle.x + _COS(ind + 1), circle.y + _SIN(ind + 1), g26_layer_height };
+
+ #if IS_KINEMATIC
+ // Check to make sure this segment is entirely on the bed, skip if not.
+ if (!position_is_reachable(p) || !position_is_reachable(q)) continue;
+ #else
+ LIMIT(p.x, X_MIN_POS + 1, X_MAX_POS - 1); // Prevent hitting the endstops
+ LIMIT(p.y, Y_MIN_POS + 1, Y_MAX_POS - 1);
+ LIMIT(q.x, X_MIN_POS + 1, X_MAX_POS - 1);
+ LIMIT(q.y, Y_MIN_POS + 1, Y_MAX_POS - 1);
+ #endif
+
+ print_line_from_here_to_there(p, q);
+ SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
+ }
+
+ #endif // !ARC_SUPPORT
+
+ if (look_for_lines_to_connect()) goto LEAVE;
+ }
+
+ SERIAL_FLUSH(); // Prevent host M105 buffer overrun.
+
+ } while (--g26_repeats && location.valid());
+
+ LEAVE:
+ ui.set_status_P(GET_TEXT(MSG_G26_LEAVING), -1);
+
+ retract_filament(destination);
+ destination.z = Z_CLEARANCE_BETWEEN_PROBES;
+ move_to(destination, 0); // Raise the nozzle
+
+ destination = g26_xy_pos; // Move back to the starting XY position
+ move_to(destination, 0); // Move back to the starting position
+
+ #if DISABLED(NO_VOLUMETRICS)
+ parser.volumetric_enabled = volumetric_was_enabled;
+ planner.calculate_volumetric_multipliers();
+ #endif
+
+ TERN_(HAS_LCD_MENU, ui.release()); // Give back control of the LCD
+
+ if (!g26_keep_heaters_on) {
+ TERN_(HAS_HEATED_BED, thermalManager.setTargetBed(0));
+ thermalManager.setTargetHotend(active_extruder, 0);
+ }
+}
+
+#endif // G26_MESH_VALIDATION
diff --git a/Marlin/src/gcode/bedlevel/G35.cpp b/Marlin/src/gcode/bedlevel/G35.cpp
new file mode 100644
index 0000000..46f75f2
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/G35.cpp
@@ -0,0 +1,167 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(ASSISTED_TRAMMING)
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+#include "../../module/probe.h"
+#include "../../feature/bedlevel/bedlevel.h"
+
+#if HAS_MULTI_HOTEND
+ #include "../../module/tool_change.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
+#include "../../core/debug_out.h"
+
+//
+// Define tramming point names.
+//
+
+#include "../../feature/tramming.h"
+
+/**
+ * G35: Read bed corners to help adjust bed screws
+ *
+ * S<screw_thread>
+ *
+ * Screw thread: 30 - Clockwise M3
+ * 31 - Counter-Clockwise M3
+ * 40 - Clockwise M4
+ * 41 - Counter-Clockwise M4
+ * 50 - Clockwise M5
+ * 51 - Counter-Clockwise M5
+ **/
+void GcodeSuite::G35() {
+ DEBUG_SECTION(log_G35, "G35", DEBUGGING(LEVELING));
+
+ if (DEBUGGING(LEVELING)) log_machine_info();
+
+ float z_measured[G35_PROBE_COUNT] = { 0 };
+
+ const uint8_t screw_thread = parser.byteval('S', TRAMMING_SCREW_THREAD);
+ if (!WITHIN(screw_thread, 30, 51) || screw_thread % 10 > 1) {
+ SERIAL_ECHOLNPGM("?(S)crew thread must be 30, 31, 40, 41, 50, or 51.");
+ return;
+ }
+
+ // Wait for planner moves to finish!
+ planner.synchronize();
+
+ // Disable the leveling matrix before auto-aligning
+ #if HAS_LEVELING
+ TERN_(RESTORE_LEVELING_AFTER_G35, const bool leveling_was_active = planner.leveling_active);
+ set_bed_leveling_enabled(false);
+ #endif
+
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ workspace_plane = PLANE_XY;
+ #endif
+
+ // Always home with tool 0 active
+ #if HAS_MULTI_HOTEND
+ const uint8_t old_tool_index = active_extruder;
+ tool_change(0, true);
+ #endif
+
+ // Disable duplication mode on homing
+ TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false));
+
+ // Home all before this procedure
+ home_all_axes();
+
+ bool err_break = false;
+
+ // Probe all positions
+ LOOP_L_N(i, G35_PROBE_COUNT) {
+
+ // In BLTOUCH HS mode, the probe travels in a deployed state.
+ // Users of G35 might have a badly misaligned bed, so raise Z by the
+ // length of the deployed pin (BLTOUCH stroke < 7mm)
+ do_blocking_move_to_z((Z_CLEARANCE_BETWEEN_PROBES) + TERN0(BLTOUCH_HS_MODE, 7));
+ const float z_probed_height = probe.probe_at_point(screws_tilt_adjust_pos[i], PROBE_PT_RAISE, 0, true);
+
+ if (isnan(z_probed_height)) {
+ SERIAL_ECHOPAIR("G35 failed at point ", int(i), " (");
+ SERIAL_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i]));
+ SERIAL_CHAR(')');
+ SERIAL_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[i].y);
+ err_break = true;
+ break;
+ }
+
+ if (DEBUGGING(LEVELING)) {
+ DEBUG_ECHOPAIR("Probing point ", int(i), " (");
+ DEBUG_PRINT_P((char *)pgm_read_ptr(&tramming_point_name[i]));
+ DEBUG_CHAR(')');
+ DEBUG_ECHOLNPAIR_P(SP_X_STR, screws_tilt_adjust_pos[i].x, SP_Y_STR, screws_tilt_adjust_pos[i].y, SP_Z_STR, z_probed_height);
+ }
+
+ z_measured[i] = z_probed_height;
+ }
+
+ if (!err_break) {
+ const float threads_factor[] = { 0.5, 0.7, 0.8 };
+
+ // Calculate adjusts
+ LOOP_S_L_N(i, 1, G35_PROBE_COUNT) {
+ const float diff = z_measured[0] - z_measured[i],
+ adjust = abs(diff) < 0.001f ? 0 : diff / threads_factor[(screw_thread - 30) / 10];
+
+ const int full_turns = trunc(adjust);
+ const float decimal_part = adjust - float(full_turns);
+ const int minutes = trunc(decimal_part * 60.0f);
+
+ SERIAL_ECHOPGM("Turn ");
+ SERIAL_ECHOPGM_P((char *)pgm_read_ptr(&tramming_point_name[i]));
+ SERIAL_ECHOPAIR(" ", (screw_thread & 1) == (adjust > 0) ? "CCW" : "CW", " by ", abs(full_turns), " turns");
+ if (minutes) SERIAL_ECHOPAIR(" and ", abs(minutes), " minutes");
+ if (ENABLED(REPORT_TRAMMING_MM)) SERIAL_ECHOPAIR(" (", -diff, "mm)");
+ SERIAL_EOL();
+ }
+ }
+ else
+ SERIAL_ECHOLNPGM("G35 aborted.");
+
+ // Restore the active tool after homing
+ #if HAS_MULTI_HOTEND
+ tool_change(old_tool_index, DISABLED(PARKING_EXTRUDER)); // Fetch previous toolhead if not PARKING_EXTRUDER
+ #endif
+
+ #if BOTH(HAS_LEVELING, RESTORE_LEVELING_AFTER_G35)
+ set_bed_leveling_enabled(leveling_was_active);
+ #endif
+
+ // Stow the probe, as the last call to probe.probe_at_point(...) left
+ // the probe deployed if it was successful.
+ probe.stow();
+
+ move_to_tramming_wait_pos();
+
+ // After this operation the Z position needs correction
+ set_axis_never_homed(Z_AXIS);
+}
+
+#endif // ASSISTED_TRAMMING
diff --git a/Marlin/src/gcode/bedlevel/G42.cpp b/Marlin/src/gcode/bedlevel/G42.cpp
new file mode 100644
index 0000000..a2896ed
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/G42.cpp
@@ -0,0 +1,73 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_MESH
+
+#include "../gcode.h"
+#include "../../MarlinCore.h" // for IsRunning()
+#include "../../module/motion.h"
+#include "../../module/probe.h" // for probe.offset
+#include "../../feature/bedlevel/bedlevel.h"
+
+/**
+ * G42: Move X & Y axes to mesh coordinates (I & J)
+ */
+void GcodeSuite::G42() {
+ if (MOTION_CONDITIONS) {
+ 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(STR_ERR_MESH_XY);
+ return;
+ }
+
+ // Move to current_position, as modified by I, J, P parameters
+ destination = current_position;
+
+ if (hasI) destination.x = _GET_MESH_X(ix);
+ if (hasJ) destination.y = _GET_MESH_Y(iy);
+
+ #if HAS_PROBE_XY_OFFSET
+ if (parser.boolval('P')) {
+ if (hasI) destination.x -= probe.offset_xy.x;
+ if (hasJ) destination.y -= probe.offset_xy.y;
+ }
+ #endif
+
+ const feedRate_t fval = parser.linearval('F'),
+ fr_mm_s = MMM_TO_MMS(fval > 0 ? fval : 0.0f);
+
+ // SCARA kinematic has "safe" XY raw moves
+ #if IS_SCARA
+ prepare_internal_fast_move_to_destination(fr_mm_s);
+ #else
+ prepare_internal_move_to_destination(fr_mm_s);
+ #endif
+ }
+}
+
+#endif // HAS_MESH
diff --git a/Marlin/src/gcode/bedlevel/M420.cpp b/Marlin/src/gcode/bedlevel/M420.cpp
new file mode 100644
index 0000000..96122c1
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/M420.cpp
@@ -0,0 +1,245 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_LEVELING
+
+#include "../gcode.h"
+#include "../../feature/bedlevel/bedlevel.h"
+#include "../../module/planner.h"
+#include "../../module/probe.h"
+
+#if ENABLED(EEPROM_SETTINGS)
+ #include "../../module/settings.h"
+#endif
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../lcd/extui/ui_api.h"
+#endif
+
+//#define M420_C_USE_MEAN
+
+/**
+ * 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)
+ * T[map] 0:Human-readable 1:CSV 2:"LCD" 4:Compact
+ *
+ * With mesh-based leveling only:
+ *
+ * C Center mesh on the mean of the lowest and highest
+ *
+ * With MARLIN_DEV_MODE:
+ * S2 Create a simple random mesh and enable
+ */
+void GcodeSuite::M420() {
+ const bool seen_S = parser.seen('S'),
+ to_enable = seen_S ? parser.value_bool() : planner.leveling_active;
+
+ #if ENABLED(MARLIN_DEV_MODE)
+ if (parser.intval('S') == 2) {
+ const float x_min = probe.min_x(), x_max = probe.max_x(),
+ y_min = probe.min_y(), y_max = probe.max_y();
+ #if ENABLED(AUTO_BED_LEVELING_BILINEAR)
+ bilinear_start.set(x_min, y_min);
+ bilinear_grid_spacing.set((x_max - x_min) / (GRID_MAX_POINTS_X - 1),
+ (y_max - y_min) / (GRID_MAX_POINTS_Y - 1));
+ #endif
+ GRID_LOOP(x, y) {
+ Z_VALUES(x, y) = 0.001 * random(-200, 200);
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, Z_VALUES(x, y)));
+ }
+ SERIAL_ECHOPGM("Simulated " STRINGIFY(GRID_MAX_POINTS_X) "x" STRINGIFY(GRID_MAX_POINTS_Y) " mesh ");
+ SERIAL_ECHOPAIR(" (", x_min);
+ SERIAL_CHAR(','); SERIAL_ECHO(y_min);
+ SERIAL_ECHOPAIR(")-(", x_max);
+ SERIAL_CHAR(','); SERIAL_ECHO(y_max);
+ SERIAL_ECHOLNPGM(")");
+ }
+ #endif
+
+ xyz_pos_t oldpos = current_position;
+
+ // If disabling leveling do it right away
+ // (Don't disable for just M420 or M420 V)
+ if (seen_S && !to_enable) set_bed_leveling_enabled(false);
+
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+
+ // L to load a mesh from the EEPROM
+ if (parser.seen('L')) {
+
+ set_bed_leveling_enabled(false);
+
+ #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_ECHOLNPGM("?EEPROM storage not available.");
+ return;
+ }
+
+ if (!WITHIN(storage_slot, 0, a - 1)) {
+ SERIAL_ECHOLNPGM("?Invalid storage slot.");
+ SERIAL_ECHOLNPAIR("?Use 0 to ", a - 1);
+ return;
+ }
+
+ settings.load_mesh(storage_slot);
+ ubl.storage_slot = storage_slot;
+
+ #else
+
+ SERIAL_ECHOLNPGM("?EEPROM storage not available.");
+ return;
+
+ #endif
+ }
+
+ // L or V display the map info
+ if (parser.seen("LV")) {
+ ubl.display_map(parser.byteval('T'));
+ SERIAL_ECHOPGM("Mesh is ");
+ if (!ubl.mesh_is_valid()) SERIAL_ECHOPGM("in");
+ SERIAL_ECHOLNPAIR("valid\nStorage slot: ", ubl.storage_slot);
+ }
+
+ #endif // AUTO_BED_LEVELING_UBL
+
+ const bool seenV = parser.seen('V');
+
+ #if HAS_MESH
+
+ if (leveling_is_valid()) {
+
+ // Subtract the given value or the mean from all mesh values
+ if (parser.seen('C')) {
+ const float cval = parser.value_float();
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+
+ set_bed_leveling_enabled(false);
+ ubl.adjust_mesh_to_mean(true, cval);
+
+ #else
+
+ #if ENABLED(M420_C_USE_MEAN)
+
+ // Get the sum and average of all mesh values
+ float mesh_sum = 0;
+ GRID_LOOP(x, y) mesh_sum += Z_VALUES(x, y);
+ const float zmean = mesh_sum / float(GRID_MAX_POINTS);
+
+ #else
+
+ // Find the low and high mesh values
+ float lo_val = 100, hi_val = -100;
+ GRID_LOOP(x, y) {
+ const float z = Z_VALUES(x, y);
+ NOMORE(lo_val, z);
+ NOLESS(hi_val, z);
+ }
+ // Take the mean of the lowest and highest
+ const float zmean = (lo_val + hi_val) / 2.0 + cval;
+
+ #endif
+
+ // If not very close to 0, adjust the mesh
+ if (!NEAR_ZERO(zmean)) {
+ set_bed_leveling_enabled(false);
+ // Subtract the mean from all values
+ GRID_LOOP(x, y) {
+ Z_VALUES(x, y) -= zmean;
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, Z_VALUES(x, y)));
+ }
+ TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate());
+ }
+
+ #endif
+ }
+
+ }
+ else if (to_enable || seenV) {
+ SERIAL_ECHO_MSG("Invalid mesh.");
+ goto EXIT_M420;
+ }
+
+ #endif // HAS_MESH
+
+ // V to print the matrix or mesh
+ if (seenV) {
+ #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();
+ TERN_(ABL_BILINEAR_SUBDIVISION, print_bilinear_leveling_grid_virt());
+ #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
+
+ // Enable leveling if specified, or if previously active
+ set_bed_leveling_enabled(to_enable);
+
+ #if HAS_MESH
+ EXIT_M420:
+ #endif
+
+ // Error if leveling failed to enable or reenable
+ if (to_enable && !planner.leveling_active)
+ SERIAL_ERROR_MSG(STR_ERR_M420_FAILED);
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("Bed Leveling ");
+ serialprintln_onoff(planner.leveling_active);
+
+ #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(STR_OFF);
+ #endif
+
+ // Report change in position
+ if (oldpos != current_position)
+ report_current_position();
+}
+
+#endif // HAS_LEVELING
diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp
new file mode 100644
index 0000000..2e80f09
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp
@@ -0,0 +1,901 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * G29.cpp - Auto Bed Leveling
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_ABL_NOT_UBL
+
+#include "../../gcode.h"
+#include "../../../feature/bedlevel/bedlevel.h"
+#include "../../../module/motion.h"
+#include "../../../module/planner.h"
+#include "../../../module/stepper.h"
+#include "../../../module/probe.h"
+#include "../../queue.h"
+
+#if ENABLED(PROBE_TEMP_COMPENSATION)
+ #include "../../../feature/probe_temp_comp.h"
+ #include "../../../module/temperature.h"
+#endif
+
+#if HAS_DISPLAY
+ #include "../../../lcd/marlinui.h"
+#endif
+
+#if ENABLED(AUTO_BED_LEVELING_LINEAR)
+ #include "../../../libs/least_squares_fit.h"
+#endif
+
+#if ABL_PLANAR
+ #include "../../../libs/vector_3.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
+#include "../../../core/debug_out.h"
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../../lcd/extui/ui_api.h"
+#endif
+
+#if ENABLED(DWIN_CREALITY_LCD)
+ #include "../../../lcd/dwin/e3v2/dwin.h"
+#endif
+
+#if HAS_MULTI_HOTEND
+ #include "../../../module/tool_change.h"
+#endif
+
+#if ABL_GRID
+ #if ENABLED(PROBE_Y_FIRST)
+ #define PR_OUTER_VAR meshCount.x
+ #define PR_OUTER_END abl_grid_points.x
+ #define PR_INNER_VAR meshCount.y
+ #define PR_INNER_END abl_grid_points.y
+ #else
+ #define PR_OUTER_VAR meshCount.y
+ #define PR_OUTER_END abl_grid_points.y
+ #define PR_INNER_VAR meshCount.x
+ #define PR_INNER_END abl_grid_points.x
+ #endif
+#endif
+
+#define G29_RETURN(b) return TERN_(G29_RETRY_AND_RECOVER, b)
+
+/**
+ * G29: Detailed Z probe, probes the bed at 3 or more points.
+ * Will fail if the printer has not been homed with G28.
+ *
+ * Enhanced G29 Auto Bed Leveling Probe Routine
+ *
+ * O Auto-level only if needed
+ *
+ * D Dry-Run mode. Just evaluate the bed Topology - Don't apply
+ * or alter the bed level data. Useful to check the topology
+ * after a first run of G29.
+ *
+ * J Jettison current bed leveling data
+ *
+ * V Set the verbose level (0-4). Example: "G29 V3"
+ *
+ * Parameters With LINEAR leveling only:
+ *
+ * P Set the size of the grid that will be probed (P x P points).
+ * Example: "G29 P4"
+ *
+ * X Set the X size of the grid that will be probed (X x Y points).
+ * Example: "G29 X7 Y5"
+ *
+ * Y Set the Y size of the grid that will be probed (X x Y points).
+ *
+ * T Generate a Bed Topology Report. Example: "G29 P5 T" for a detailed report.
+ * This is useful for manual bed leveling and finding flaws in the bed (to
+ * assist with part placement).
+ * Not supported by non-linear delta printer bed leveling.
+ *
+ * Parameters With LINEAR and BILINEAR leveling only:
+ *
+ * S Set the XY travel speed between probe points (in units/min)
+ *
+ * H Set bounds to a centered square H x H units in size
+ *
+ * -or-
+ *
+ * F Set the Front limit of the probing grid
+ * B Set the Back limit of the probing grid
+ * L Set the Left limit of the probing grid
+ * R Set the Right limit of the probing grid
+ *
+ * Parameters with DEBUG_LEVELING_FEATURE only:
+ *
+ * C Make a totally fake grid with no actual probing.
+ * For use in testing when no probing is possible.
+ *
+ * Parameters with BILINEAR leveling only:
+ *
+ * Z Supply an additional Z probe offset
+ *
+ * Extra parameters with PROBE_MANUALLY:
+ *
+ * To do manual probing simply repeat G29 until the procedure is complete.
+ * The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort.
+ *
+ * Q Query leveling and G29 state
+ *
+ * A Abort current leveling procedure
+ *
+ * Extra parameters with BILINEAR only:
+ *
+ * W Write a mesh point. (If G29 is idle.)
+ * I X index for mesh point
+ * J Y index for mesh point
+ * X X for mesh point, overrides I
+ * Y Y for mesh point, overrides J
+ * Z Z for mesh point. Otherwise, raw current Z.
+ *
+ * Without PROBE_MANUALLY:
+ *
+ * E By default G29 will engage the Z probe, test the bed, then disengage.
+ * Include "E" to engage/disengage the Z probe for each sample.
+ * There's no extra effect if you have a fixed Z probe.
+ */
+G29_TYPE GcodeSuite::G29() {
+
+ reset_stepper_timeout();
+
+ const bool seenQ = EITHER(DEBUG_LEVELING_FEATURE, PROBE_MANUALLY) && parser.seen('Q');
+
+ // 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 |= MARLIN_DEBUG_LEVELING;
+ DEBUG_SECTION(log_G29, "G29", DEBUGGING(LEVELING));
+ if (DEBUGGING(LEVELING)) log_machine_info();
+ marlin_debug_flags = old_debug_flags;
+ if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false);
+ #endif
+
+ const bool seenA = TERN0(PROBE_MANUALLY, parser.seen('A')),
+ no_action = seenA || seenQ,
+ faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action;
+
+ if (!no_action && planner.leveling_active && parser.boolval('O')) { // Auto-level only if needed
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip");
+ G29_RETURN(false);
+ }
+
+ // Send 'N' to force homing before G29 (internal only)
+ if (parser.seen('N'))
+ process_subcommands_now_P(TERN(G28_L0_ENSURES_LEVELING_OFF, PSTR("G28L0"), G28_STR));
+
+ // Don't allow auto-leveling without homing first
+ if (homing_needed_error()) G29_RETURN(false);
+
+ // Define local vars 'static' for manual probing, 'auto' otherwise
+ #define ABL_VAR TERN_(PROBE_MANUALLY, static)
+
+ ABL_VAR int verbose_level;
+ ABL_VAR xy_pos_t probePos;
+ ABL_VAR float measured_z;
+ ABL_VAR bool dryrun, abl_should_enable;
+
+ #if EITHER(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR)
+ ABL_VAR int abl_probe_index;
+ #endif
+
+ #if ABL_GRID
+
+ #if ENABLED(PROBE_MANUALLY)
+ ABL_VAR xy_int8_t meshCount;
+ #endif
+
+ ABL_VAR xy_pos_t probe_position_lf, probe_position_rb;
+ ABL_VAR xy_float_t gridSpacing = { 0, 0 };
+
+ #if ENABLED(AUTO_BED_LEVELING_LINEAR)
+ ABL_VAR bool do_topography_map;
+ ABL_VAR xy_uint8_t abl_grid_points;
+ #else // Bilinear
+ constexpr xy_uint8_t abl_grid_points = { GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y };
+ #endif
+
+ #if ENABLED(AUTO_BED_LEVELING_LINEAR)
+ ABL_VAR int abl_points;
+ #else
+ int 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)
+ int constexpr abl_points = 3; // used to show total points
+ #endif
+
+ vector_3 points[3];
+ probe.get_three_points(points);
+
+ #endif // AUTO_BED_LEVELING_3POINT
+
+ #if ENABLED(AUTO_BED_LEVELING_LINEAR)
+ struct linear_fit_data lsf_results;
+ #endif
+
+ /**
+ * On the initial G29 fetch command parameters.
+ */
+ if (!g29_in_progress) {
+
+ TERN_(HAS_MULTI_HOTEND, if (active_extruder) tool_change(0));
+
+ #if EITHER(PROBE_MANUALLY, 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_MSG("No bilinear grid");
+ G29_RETURN(false);
+ }
+
+ const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z;
+ if (!WITHIN(rz, -10, 10)) {
+ SERIAL_ERROR_MSG("Bad Z value");
+ G29_RETURN(false);
+ }
+
+ 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 + 0.5 * gridSpacing.x) / gridSpacing.x;
+ j = (ry - bilinear_start.y + 0.5 * gridSpacing.y) / gridSpacing.y;
+ LIMIT(i, 0, GRID_MAX_POINTS_X - 1);
+ LIMIT(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;
+ TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate());
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz));
+ set_bed_leveling_enabled(abl_should_enable);
+ if (abl_should_enable) report_current_position();
+ }
+ G29_RETURN(false);
+ } // parser.seen('W')
+
+ #else
+
+ constexpr bool seen_w = false;
+
+ #endif
+
+ // Jettison bed leveling data
+ if (!seen_w && parser.seen('J')) {
+ reset_bed_level();
+ G29_RETURN(false);
+ }
+
+ verbose_level = parser.intval('V');
+ if (!WITHIN(verbose_level, 0, 4)) {
+ SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).");
+ G29_RETURN(false);
+ }
+
+ dryrun = parser.boolval('D') || TERN0(PROBE_MANUALLY, no_action);
+
+ #if ENABLED(AUTO_BED_LEVELING_LINEAR)
+
+ incremental_LSF_reset(&lsf_results);
+
+ 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.set(
+ parser.byteval('X', GRID_MAX_POINTS_X),
+ parser.byteval('Y', GRID_MAX_POINTS_Y)
+ );
+ if (parser.seenval('P')) abl_grid_points.x = abl_grid_points.y = parser.value_int();
+
+ if (!WITHIN(abl_grid_points.x, 2, GRID_MAX_POINTS_X)) {
+ SERIAL_ECHOLNPGM("?Probe points (X) implausible (2-" STRINGIFY(GRID_MAX_POINTS_X) ").");
+ G29_RETURN(false);
+ }
+ if (!WITHIN(abl_grid_points.y, 2, GRID_MAX_POINTS_Y)) {
+ SERIAL_ECHOLNPGM("?Probe points (Y) implausible (2-" STRINGIFY(GRID_MAX_POINTS_Y) ").");
+ G29_RETURN(false);
+ }
+
+ 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));
+
+ const float x_min = probe.min_x(), x_max = probe.max_x(),
+ y_min = probe.min_y(), y_max = probe.max_y();
+
+ if (parser.seen('H')) {
+ const int16_t size = (int16_t)parser.value_linear_units();
+ probe_position_lf.set(_MAX((X_CENTER) - size / 2, x_min), _MAX((Y_CENTER) - size / 2, y_min));
+ probe_position_rb.set(_MIN(probe_position_lf.x + size, x_max), _MIN(probe_position_lf.y + size, y_max));
+ }
+ else {
+ probe_position_lf.set(parser.linearval('L', x_min), parser.linearval('F', y_min));
+ probe_position_rb.set(parser.linearval('R', x_max), parser.linearval('B', y_max));
+ }
+
+ if (!probe.good_bounds(probe_position_lf, probe_position_rb)) {
+ if (DEBUGGING(LEVELING)) {
+ DEBUG_ECHOLNPAIR("G29 L", probe_position_lf.x, " R", probe_position_rb.x,
+ " F", probe_position_lf.y, " B", probe_position_rb.y);
+ }
+ SERIAL_ECHOLNPGM("? (L,R,F,B) out of bounds.");
+ G29_RETURN(false);
+ }
+
+ // Probe at the points of a lattice grid
+ gridSpacing.set((probe_position_rb.x - probe_position_lf.x) / (abl_grid_points.x - 1),
+ (probe_position_rb.y - probe_position_lf.y) / (abl_grid_points.y - 1));
+
+ #endif // ABL_GRID
+
+ if (verbose_level > 0) {
+ SERIAL_ECHOPGM("G29 Auto Bed Leveling");
+ if (dryrun) SERIAL_ECHOPGM(" (DRYRUN)");
+ SERIAL_EOL();
+ }
+
+ planner.synchronize();
+
+ #if ENABLED(AUTO_BED_LEVELING_3POINT)
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> 3-point Leveling");
+ points[0].z = points[1].z = points[2].z = 0; // Probe at 3 arbitrary points
+ #endif
+
+ #if BOTH(AUTO_BED_LEVELING_BILINEAR, EXTENSIBLE_UI)
+ ExtUI::onMeshLevelingStart();
+ #endif
+
+ if (!faux) {
+ remember_feedrate_scaling_off();
+
+ #if ENABLED(PREHEAT_BEFORE_LEVELING)
+ if (!dryrun) probe.preheat_for_probing(LEVELING_NOZZLE_TEMP, LEVELING_BED_TEMP);
+ #endif
+ }
+
+ // Disable auto bed leveling during G29.
+ // Be formal so G29 can be done successively without G28.
+ if (!no_action) set_bed_leveling_enabled(false);
+
+ // Deploy certain probes before starting probing
+ #if HAS_BED_PROBE
+ if (ENABLED(BLTOUCH))
+ do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE);
+ else if (probe.deploy()) {
+ set_bed_leveling_enabled(abl_should_enable);
+ G29_RETURN(false);
+ }
+ #endif
+
+ #if ENABLED(AUTO_BED_LEVELING_BILINEAR)
+ if (TERN1(PROBE_MANUALLY, !no_action)
+ && (gridSpacing != bilinear_grid_spacing || probe_position_lf != bilinear_start)
+ ) {
+ // Reset grid to 0.0 or "not probed". (Also disables ABL)
+ reset_bed_level();
+
+ // Initialize a grid with the given dimensions
+ bilinear_grid_spacing = gridSpacing;
+ bilinear_start = probe_position_lf;
+
+ // Can't re-enable (on error) until the new grid is written
+ abl_should_enable = false;
+ }
+ #endif // AUTO_BED_LEVELING_BILINEAR
+
+ } // !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_ECHOLNPGM("Manual G29 aborted");
+ SET_SOFT_ENDSTOP_LOOSE(false);
+ set_bed_leveling_enabled(abl_should_enable);
+ g29_in_progress = false;
+ TERN_(LCD_BED_LEVELING, ui.wait_for_move = false);
+ }
+
+ // Query G29 status
+ if (verbose_level || seenQ) {
+ SERIAL_ECHOPGM("Manual G29 ");
+ if (g29_in_progress) {
+ SERIAL_ECHOPAIR("point ", _MIN(abl_probe_index + 1, abl_points));
+ SERIAL_ECHOLNPAIR(" of ", abl_points);
+ }
+ else
+ SERIAL_ECHOLNPGM("idle");
+ }
+
+ if (no_action) G29_RETURN(false);
+
+ if (abl_probe_index == 0) {
+ // For the initial G29 S2 save software endstop state
+ SET_SOFT_ENDSTOP_LOOSE(true);
+ // Move close to the bed before the first point
+ do_blocking_move_to_z(0);
+ }
+ else {
+
+ #if EITHER(AUTO_BED_LEVELING_LINEAR, 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;
+
+ #if ENABLED(AUTO_BED_LEVELING_LINEAR)
+
+ mean += measured_z;
+ eqnBVector[index] = measured_z;
+ eqnAMatrix[index + 0 * abl_points] = probePos.x;
+ eqnAMatrix[index + 1 * abl_points] = probePos.y;
+ eqnAMatrix[index + 2 * abl_points] = 1;
+
+ incremental_LSF(&lsf_results, probePos, measured_z);
+
+ #elif ENABLED(AUTO_BED_LEVELING_3POINT)
+
+ points[index].z = measured_z;
+
+ #elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+ const float newz = measured_z + zoffset;
+ z_values[meshCount.x][meshCount.y] = newz;
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(meshCount, newz));
+
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR_P(PSTR("Save X"), meshCount.x, SP_Y_STR, meshCount.y, SP_Z_STR, measured_z + zoffset);
+
+ #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 meshCount.x, meshCount.y 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
+ const bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_END) & 1);
+ if (zig) PR_INNER_VAR = (PR_INNER_END - 1) - PR_INNER_VAR;
+
+ probePos = probe_position_lf + gridSpacing * meshCount.asFloat();
+
+ TERN_(AUTO_BED_LEVELING_LINEAR, indexIntoAB[meshCount.x][meshCount.y] = abl_probe_index);
+
+ // Keep looping till a reachable point is found
+ if (position_is_reachable(probePos)) break;
+ ++abl_probe_index;
+ }
+
+ // Is there a next point to move to?
+ if (abl_probe_index < abl_points) {
+ _manual_goto_xy(probePos); // Can be used here too!
+ // Disable software endstops to allow manual adjustment
+ // If G29 is not completed, they will not be re-enabled
+ SET_SOFT_ENDSTOP_LOOSE(true);
+ G29_RETURN(false);
+ }
+ else {
+ // Leveling done! Fall through to G29 finishing code below
+ SERIAL_ECHOLNPGM("Grid probing done.");
+ // Re-enable software endstops, if needed
+ SET_SOFT_ENDSTOP_LOOSE(false);
+ }
+
+ #elif ENABLED(AUTO_BED_LEVELING_3POINT)
+
+ // Probe at 3 arbitrary points
+ if (abl_probe_index < abl_points) {
+ probePos = points[abl_probe_index];
+ _manual_goto_xy(probePos);
+ // Disable software endstops to allow manual adjustment
+ // If G29 is not completed, they will not be re-enabled
+ SET_SOFT_ENDSTOP_LOOSE(true);
+ G29_RETURN(false);
+ }
+ else {
+
+ SERIAL_ECHOLNPGM("3-point probing done.");
+
+ // Re-enable software endstops, if needed
+ SET_SOFT_ENDSTOP_LOOSE(false);
+
+ if (!dryrun) {
+ vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal();
+ if (planeNormal.z < 0) planeNormal *= -1;
+ planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal);
+
+ // Can't re-enable (on error) until the new grid is written
+ abl_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;
+
+ xy_int8_t meshCount;
+
+ // Outer loop is X with PROBE_Y_FIRST enabled
+ // Outer loop is Y with PROBE_Y_FIRST disabled
+ for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_END && !isnan(measured_z); PR_OUTER_VAR++) {
+
+ int8_t inStart, inStop, inInc;
+
+ if (zig) { // Zig away from origin
+ inStart = 0; // Left or front
+ inStop = PR_INNER_END; // Right or back
+ inInc = 1; // Zig right
+ }
+ else { // Zag towards origin
+ inStart = PR_INNER_END - 1; // Right or back
+ inStop = -1; // Left or front
+ inInc = -1; // Zag left
+ }
+
+ zig ^= true; // zag
+
+ // An index to print current state
+ uint8_t pt_index = (PR_OUTER_VAR) * (PR_INNER_END) + 1;
+
+ // Inner loop is Y with PROBE_Y_FIRST enabled
+ // Inner loop is X with PROBE_Y_FIRST disabled
+ for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; pt_index++, PR_INNER_VAR += inInc) {
+
+ probePos = probe_position_lf + gridSpacing * meshCount.asFloat();
+
+ TERN_(AUTO_BED_LEVELING_LINEAR, indexIntoAB[meshCount.x][meshCount.y] = ++abl_probe_index); // 0...
+
+ // Avoid probing outside the round or hexagonal area
+ if (TERN0(IS_KINEMATIC, !probe.can_reach(probePos))) continue;
+
+ if (verbose_level) SERIAL_ECHOLNPAIR("Probing mesh point ", int(pt_index), "/", abl_points, ".");
+ TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/%i"), GET_TEXT(MSG_PROBING_MESH), int(pt_index), int(abl_points)));
+
+ measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(probePos, raise_after, verbose_level);
+
+ if (isnan(measured_z)) {
+ set_bed_leveling_enabled(abl_should_enable);
+ break; // Breaks out of both loops
+ }
+
+ #if ENABLED(PROBE_TEMP_COMPENSATION)
+ temp_comp.compensate_measurement(TSI_BED, thermalManager.degBed(), measured_z);
+ temp_comp.compensate_measurement(TSI_PROBE, thermalManager.degProbe(), measured_z);
+ TERN_(USE_TEMP_EXT_COMPENSATION, temp_comp.compensate_measurement(TSI_EXT, thermalManager.degHotend(), measured_z));
+ #endif
+
+ #if ENABLED(AUTO_BED_LEVELING_LINEAR)
+
+ mean += measured_z;
+ eqnBVector[abl_probe_index] = measured_z;
+ eqnAMatrix[abl_probe_index + 0 * abl_points] = probePos.x;
+ eqnAMatrix[abl_probe_index + 1 * abl_points] = probePos.y;
+ eqnAMatrix[abl_probe_index + 2 * abl_points] = 1;
+
+ incremental_LSF(&lsf_results, probePos, measured_z);
+
+ #elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+ const float z = measured_z + zoffset;
+ z_values[meshCount.x][meshCount.y] = z;
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(meshCount, z));
+
+ #endif
+
+ abl_should_enable = false;
+ idle_no_sleep();
+
+ } // inner
+ } // outer
+
+ #elif ENABLED(AUTO_BED_LEVELING_3POINT)
+
+ // Probe at 3 arbitrary points
+
+ LOOP_L_N(i, 3) {
+ if (verbose_level) SERIAL_ECHOLNPAIR("Probing point ", int(i + 1), "/3.");
+ TERN_(HAS_DISPLAY, ui.status_printf_P(0, PSTR(S_FMT " %i/3"), GET_TEXT(MSG_PROBING_MESH), int(i + 1)));
+
+ // Retain the last probe position
+ probePos = points[i];
+ measured_z = faux ? 0.001 * random(-100, 101) : probe.probe_at_point(probePos, 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 *= -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
+
+ TERN_(HAS_DISPLAY, ui.reset_status());
+
+ // Stow the probe. No raise for FIX_MOUNTED_PROBE.
+ if (probe.stow()) {
+ 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 (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position);
+
+ #if ENABLED(PROBE_MANUALLY)
+ g29_in_progress = false;
+ TERN_(LCD_BED_LEVELING, ui.wait_for_move = false);
+ #endif
+
+ // Calculate leveling, print reports, correct the position
+ if (!isnan(measured_z)) {
+ #if ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+ if (!dryrun) extrapolate_unprobed_bed_level();
+ print_bilinear_leveling_grid();
+
+ refresh_bed_level();
+
+ TERN_(ABL_BILINEAR_SUBDIVISION, print_bilinear_leveling_grid_virt());
+
+ #elif ENABLED(AUTO_BED_LEVELING_LINEAR)
+
+ // For LINEAR leveling calculate matrix, print reports, correct the position
+
+ /**
+ * solve the plane equation ax + by + d = z
+ * A is the matrix with rows [x y 1] for all the probed points
+ * B is the vector of the Z positions
+ * the normal vector to the plane is formed by the coefficients of the
+ * plane equation in the standard form, which is Vx*x+Vy*y+Vz*z+d = 0
+ * so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z
+ */
+ struct { float a, b, d; } plane_equation_coefficients;
+
+ finish_incremental_LSF(&lsf_results);
+ plane_equation_coefficients.a = -lsf_results.A; // We should be able to eliminate the '-' on these three lines and down below
+ plane_equation_coefficients.b = -lsf_results.B; // but that is not yet tested.
+ plane_equation_coefficients.d = -lsf_results.D;
+
+ mean /= abl_points;
+
+ if (verbose_level) {
+ SERIAL_ECHOPAIR_F("Eqn coefficients: a: ", plane_equation_coefficients.a, 8);
+ SERIAL_ECHOPAIR_F(" b: ", plane_equation_coefficients.b, 8);
+ SERIAL_ECHOPAIR_F(" d: ", plane_equation_coefficients.d, 8);
+ if (verbose_level > 2)
+ SERIAL_ECHOPAIR_F("\nMean of sampled points: ", 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.a, -plane_equation_coefficients.b, 1) // We can eliminate the '-' here and up above
+ );
+
+ // Show the Topography map if enabled
+ if (do_topography_map) {
+
+ float min_diff = 999;
+
+ auto print_topo_map = [&](PGM_P const title, const bool get_min) {
+ serialprintPGM(title);
+ for (int8_t yy = abl_grid_points.y - 1; yy >= 0; yy--) {
+ LOOP_L_N(xx, abl_grid_points.x) {
+ const int ind = indexIntoAB[xx][yy];
+ xyz_float_t tmp = { eqnAMatrix[ind + 0 * abl_points],
+ eqnAMatrix[ind + 1 * abl_points], 0 };
+ apply_rotation_xyz(planner.bed_level_matrix, tmp);
+ if (get_min) NOMORE(min_diff, eqnBVector[ind] - tmp.z);
+ const float subval = get_min ? mean : tmp.z + min_diff,
+ diff = eqnBVector[ind] - subval;
+ SERIAL_CHAR(' '); if (diff >= 0.0) SERIAL_CHAR('+'); // Include + for column alignment
+ SERIAL_ECHO_F(diff, 5);
+ } // xx
+ SERIAL_EOL();
+ } // yy
+ SERIAL_EOL();
+ };
+
+ print_topo_map(PSTR("\nBed Height Topography:\n"
+ " +--- BACK --+\n"
+ " | |\n"
+ " L | (+) | R\n"
+ " E | | I\n"
+ " F | (-) N (+) | G\n"
+ " T | | H\n"
+ " | (-) | T\n"
+ " | |\n"
+ " O-- FRONT --+\n"
+ " (0,0)\n"), true);
+ if (verbose_level > 3)
+ print_topo_map(PSTR("\nCorrected Bed Height vs. Bed Topology:\n"), false);
+
+ } //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 (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position);
+
+ xyze_pos_t converted = current_position;
+ planner.force_unapply_leveling(converted); // use conversion machinery
+
+ // Use the last measured distance to the bed, if possible
+ if ( NEAR(current_position.x, probePos.x - probe.offset_xy.x)
+ && NEAR(current_position.y, probePos.y - probe.offset_xy.y)
+ ) {
+ const float simple_z = current_position.z - measured_z;
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Probed Z", simple_z, " Matrix Z", converted.z, " Discrepancy ", simple_z - converted.z);
+ converted.z = simple_z;
+ }
+
+ // The rotated XY and corrected Z are now current_position
+ current_position = converted;
+
+ if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position);
+ }
+
+ #elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+ if (!dryrun) {
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("G29 uncorrected Z:", current_position.z);
+
+ // Unapply the offset because it is going to be immediately applied
+ // and cause compensation movement in Z
+ const float fade_scaling_factor = TERN(ENABLE_LEVELING_FADE_HEIGHT, planner.fade_scaling_factor_for_z(current_position.z), 1);
+ current_position.z -= fade_scaling_factor * bilinear_z_offset(current_position);
+
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR(" corrected Z:", current_position.z);
+ }
+
+ #endif // ABL_PLANAR
+
+ // 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) restore_feedrate_and_scaling();
+
+ // Sync the planner from the current_position
+ if (planner.leveling_active) sync_plan_position();
+
+ #if HAS_BED_PROBE
+ probe.move_z_after_probing();
+ #endif
+
+ #ifdef Z_PROBE_END_SCRIPT
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT);
+ planner.synchronize();
+ process_subcommands_now_P(PSTR(Z_PROBE_END_SCRIPT));
+ #endif
+
+ #if ENABLED(DWIN_CREALITY_LCD)
+ DWIN_CompletedLeveling();
+ #endif
+
+ report_current_position();
+
+ G29_RETURN(isnan(measured_z));
+}
+
+#endif // HAS_ABL_NOT_UBL
diff --git a/Marlin/src/gcode/bedlevel/abl/M421.cpp b/Marlin/src/gcode/bedlevel/abl/M421.cpp
new file mode 100644
index 0000000..182dc32
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/abl/M421.cpp
@@ -0,0 +1,74 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * M421.cpp - Auto Bed Leveling
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
+
+#include "../../gcode.h"
+#include "../../../feature/bedlevel/bedlevel.h"
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../../lcd/extui/ui_api.h"
+#endif
+
+/**
+ * M421: Set one or more Mesh Bed Leveling Z coordinates
+ *
+ * Usage:
+ * M421 I<xindex> J<yindex> Z<linear>
+ * M421 I<xindex> J<yindex> Q<offset>
+ *
+ * - If I is omitted, set the entire row
+ * - If J is omitted, set the entire column
+ * - If both I and J are omitted, set all
+ */
+void GcodeSuite::M421() {
+ int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1);
+ const bool hasZ = parser.seenval('Z'),
+ hasQ = !hasZ && parser.seenval('Q');
+
+ if (hasZ || hasQ) {
+ if (WITHIN(ix, -1, GRID_MAX_POINTS_X - 1) && WITHIN(iy, -1, GRID_MAX_POINTS_Y - 1)) {
+ const float zval = parser.value_linear_units();
+ uint8_t sx = ix >= 0 ? ix : 0, ex = ix >= 0 ? ix : GRID_MAX_POINTS_X - 1,
+ sy = iy >= 0 ? iy : 0, ey = iy >= 0 ? iy : GRID_MAX_POINTS_Y - 1;
+ LOOP_S_LE_N(x, sx, ex) {
+ LOOP_S_LE_N(y, sy, ey) {
+ z_values[x][y] = zval + (hasQ ? z_values[x][y] : 0);
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y]));
+ }
+ }
+ TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate());
+ }
+ else
+ SERIAL_ERROR_MSG(STR_ERR_MESH_XY);
+ }
+ else
+ SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS);
+}
+
+#endif // AUTO_BED_LEVELING_BILINEAR
diff --git a/Marlin/src/gcode/bedlevel/mbl/G29.cpp b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
new file mode 100644
index 0000000..cf27c14
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/mbl/G29.cpp
@@ -0,0 +1,193 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * G29.cpp - Mesh Bed Leveling
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(MESH_BED_LEVELING)
+
+#include "../../../feature/bedlevel/bedlevel.h"
+
+#include "../../gcode.h"
+#include "../../queue.h"
+
+#include "../../../libs/buzzer.h"
+#include "../../../lcd/marlinui.h"
+#include "../../../module/motion.h"
+#include "../../../module/stepper.h"
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../../lcd/extui/ui_api.h"
+#endif
+
+// Save 130 bytes with non-duplication of PSTR
+inline void echo_not_entered(const char c) { SERIAL_CHAR(c); SERIAL_ECHOLNPGM(" 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 Report the current mesh values
+ * S1 Start probing mesh points
+ * S2 Probe the next mesh point
+ * S3 In Jn 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
+ */
+void GcodeSuite::G29() {
+
+ static int mbl_probe_index = -1;
+
+ MeshLevelingState state = (MeshLevelingState)parser.byteval('S', (int8_t)MeshReport);
+ if (!WITHIN(state, 0, 5)) {
+ SERIAL_ECHOLNPGM("S out of range (0-5).");
+ return;
+ }
+
+ int8_t ix, iy;
+
+ switch (state) {
+ case MeshReport:
+ SERIAL_ECHOPGM("Mesh Bed Leveling ");
+ if (leveling_is_valid()) {
+ serialprintln_onoff(planner.leveling_active);
+ mbl.report_mesh();
+ }
+ else
+ SERIAL_ECHOLNPGM("has no data.");
+ break;
+
+ case MeshStart:
+ mbl.reset();
+ mbl_probe_index = 0;
+ if (!ui.wait_for_move) {
+ queue.inject_P(parser.seen('N') ? PSTR("G28" TERN(G28_L0_ENSURES_LEVELING_OFF, "L0", "") "\nG29S2") : PSTR("G29S2"));
+ return;
+ }
+ state = MeshNext;
+
+ case MeshNext:
+ if (mbl_probe_index < 0) {
+ SERIAL_ECHOLNPGM("Start mesh probing with \"G29 S1\" first.");
+ return;
+ }
+ // For each G29 S2...
+ if (mbl_probe_index == 0) {
+ // Move close to the bed before the first point
+ do_blocking_move_to_z(0);
+ }
+ else {
+ // Save Z for the previous mesh position
+ mbl.set_zigzag_z(mbl_probe_index - 1, current_position.z);
+ SET_SOFT_ENDSTOP_LOOSE(false);
+ }
+ // If there's another point to sample, move there with optional lift.
+ if (mbl_probe_index < GRID_MAX_POINTS) {
+ // Disable software endstops to allow manual adjustment
+ // If G29 is left hanging without completion they won't be re-enabled!
+ SET_SOFT_ENDSTOP_LOOSE(true);
+ mbl.zigzag(mbl_probe_index++, ix, iy);
+ _manual_goto_xy({ mbl.index_to_xpos[ix], mbl.index_to_ypos[iy] });
+ }
+ else {
+ // One last "return to the bed" (as originally coded) at completion
+ current_position.z = MANUAL_PROBE_HEIGHT;
+ line_to_current_position();
+ planner.synchronize();
+
+ // After recording the last point, activate home and activate
+ mbl_probe_index = -1;
+ SERIAL_ECHOLNPGM("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 = 0;
+ line_to_current_position(homing_feedrate(Z_AXIS));
+ planner.synchronize();
+ #endif
+
+ TERN_(LCD_BED_LEVELING, ui.wait_for_move = false);
+ }
+ break;
+
+ case MeshSet:
+ if (parser.seenval('I')) {
+ ix = parser.value_int();
+ if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) {
+ SERIAL_ECHOPAIR("I out of range (0-", int(GRID_MAX_POINTS_X - 1));
+ SERIAL_ECHOLNPGM(")");
+ return;
+ }
+ }
+ else
+ return echo_not_entered('J');
+
+ if (parser.seenval('J')) {
+ iy = parser.value_int();
+ if (!WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) {
+ SERIAL_ECHOPAIR("J out of range (0-", int(GRID_MAX_POINTS_Y - 1));
+ SERIAL_ECHOLNPGM(")");
+ return;
+ }
+ }
+ else
+ return echo_not_entered('J');
+
+ if (parser.seenval('Z')) {
+ mbl.z_values[ix][iy] = parser.value_linear_units();
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, mbl.z_values[ix][iy]));
+ }
+ else
+ return echo_not_entered('Z');
+ break;
+
+ case MeshSetZOffset:
+ if (parser.seenval('Z'))
+ mbl.z_offset = parser.value_linear_units();
+ else
+ return echo_not_entered('Z');
+ break;
+
+ case MeshReset:
+ reset_bed_level();
+ break;
+
+ } // switch(state)
+
+ if (state == MeshNext) {
+ SERIAL_ECHOPAIR("MBL G29 point ", _MIN(mbl_probe_index, GRID_MAX_POINTS));
+ SERIAL_ECHOLNPAIR(" of ", int(GRID_MAX_POINTS));
+ }
+
+ report_current_position();
+}
+
+#endif // MESH_BED_LEVELING
diff --git a/Marlin/src/gcode/bedlevel/mbl/M421.cpp b/Marlin/src/gcode/bedlevel/mbl/M421.cpp
new file mode 100644
index 0000000..1368ab0
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/mbl/M421.cpp
@@ -0,0 +1,59 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * M421.cpp - Mesh Bed Leveling
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(MESH_BED_LEVELING)
+
+#include "../../gcode.h"
+#include "../../../module/motion.h"
+#include "../../../feature/bedlevel/mbl/mesh_bed_leveling.h"
+
+/**
+ * M421: Set a single Mesh Bed Leveling Z coordinate
+ *
+ * Usage:
+ * M421 X<linear> Y<linear> Z<linear>
+ * M421 X<linear> Y<linear> Q<offset>
+ * M421 I<xindex> J<yindex> Z<linear>
+ * M421 I<xindex> J<yindex> Q<offset>
+ */
+void GcodeSuite::M421() {
+ const bool hasX = parser.seen('X'), hasI = parser.seen('I');
+ const int8_t ix = hasI ? parser.value_int() : hasX ? mbl.probe_index_x(RAW_X_POSITION(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(RAW_Y_POSITION(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_MSG(STR_ERR_M421_PARAMETERS);
+ else if (ix < 0 || iy < 0)
+ SERIAL_ERROR_MSG(STR_ERR_MESH_XY);
+ else
+ mbl.set_z(ix, iy, parser.value_linear_units() + (hasQ ? mbl.z_values[ix][iy] : 0));
+}
+
+#endif // MESH_BED_LEVELING
diff --git a/Marlin/src/gcode/bedlevel/ubl/G29.cpp b/Marlin/src/gcode/bedlevel/ubl/G29.cpp
new file mode 100644
index 0000000..2ef3ab4
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/ubl/G29.cpp
@@ -0,0 +1,36 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * G29.cpp - Unified Bed Leveling
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+
+#include "../../gcode.h"
+#include "../../../feature/bedlevel/bedlevel.h"
+
+void GcodeSuite::G29() { ubl.G29(); }
+
+#endif // AUTO_BED_LEVELING_UBL
diff --git a/Marlin/src/gcode/bedlevel/ubl/M421.cpp b/Marlin/src/gcode/bedlevel/ubl/M421.cpp
new file mode 100644
index 0000000..600c1fc
--- /dev/null
+++ b/Marlin/src/gcode/bedlevel/ubl/M421.cpp
@@ -0,0 +1,70 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * unified.cpp - Unified Bed Leveling
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+
+#include "../../gcode.h"
+#include "../../../feature/bedlevel/bedlevel.h"
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../../lcd/extui/ui_api.h"
+#endif
+
+/**
+ * M421: Set a single Mesh Bed Leveling Z coordinate
+ *
+ * Usage:
+ * M421 I<xindex> J<yindex> Z<linear>
+ * M421 I<xindex> J<yindex> Q<offset>
+ * M421 I<xindex> J<yindex> N
+ * M421 C Z<linear>
+ * M421 C Q<offset>
+ */
+void GcodeSuite::M421() {
+ xy_int8_t ij = { int8_t(parser.intval('I', -1)), int8_t(parser.intval('J', -1)) };
+ const bool hasI = ij.x >= 0,
+ hasJ = ij.y >= 0,
+ hasC = parser.seen('C'),
+ hasN = parser.seen('N'),
+ hasZ = parser.seen('Z'),
+ hasQ = !hasZ && parser.seen('Q');
+
+ if (hasC) ij = ubl.find_closest_mesh_point_of_type(REAL, current_position);
+
+ if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ || hasN))
+ SERIAL_ERROR_MSG(STR_ERR_M421_PARAMETERS);
+ else if (!WITHIN(ij.x, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(ij.y, 0, GRID_MAX_POINTS_Y - 1))
+ SERIAL_ERROR_MSG(STR_ERR_MESH_XY);
+ else {
+ float &zval = ubl.z_values[ij.x][ij.y];
+ zval = hasN ? NAN : parser.value_linear_units() + (hasQ ? zval : 0);
+ TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ij.x, ij.y, zval));
+ }
+}
+
+#endif // AUTO_BED_LEVELING_UBL
diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp
new file mode 100644
index 0000000..49dee87
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/G28.cpp
@@ -0,0 +1,493 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#include "../gcode.h"
+
+#include "../../module/stepper.h"
+#include "../../module/endstops.h"
+
+#if HAS_MULTI_HOTEND
+ #include "../../module/tool_change.h"
+#endif
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#if ENABLED(SENSORLESS_HOMING)
+ #include "../../feature/tmc_util.h"
+#endif
+
+#include "../../module/probe.h"
+
+#if ENABLED(BLTOUCH)
+ #include "../../feature/bltouch.h"
+#endif
+
+#include "../../lcd/marlinui.h"
+#if ENABLED(DWIN_CREALITY_LCD)
+ #include "../../lcd/dwin/e3v2/dwin.h"
+#endif
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../lcd/extui/ui_api.h"
+#endif
+
+#if HAS_L64XX // set L6470 absolute position registers to counts
+ #include "../../libs/L64XX/L64XX_Marlin.h"
+#endif
+
+#if ENABLED(LASER_MOVE_G28_OFF)
+ #include "../../feature/spindle_laser.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
+#include "../../core/debug_out.h"
+
+#if ENABLED(QUICK_HOME)
+
+ static void quick_home_xy() {
+
+ // Pretend the current position is 0,0
+ current_position.set(0.0, 0.0);
+ sync_plan_position();
+
+ const int x_axis_home_dir = x_home_dir(active_extruder);
+
+ 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_t stealth_states {
+ tmc_enable_stallguard(stepperX)
+ , tmc_enable_stallguard(stepperY)
+ , false
+ , false
+ #if AXIS_HAS_STALLGUARD(X2)
+ || tmc_enable_stallguard(stepperX2)
+ #endif
+ , false
+ #if AXIS_HAS_STALLGUARD(Y2)
+ || tmc_enable_stallguard(stepperY2)
+ #endif
+ };
+ #endif
+
+ do_blocking_move_to_xy(1.5 * mlx * x_axis_home_dir, 1.5 * mly * home_dir(Y_AXIS), fr_mm_s);
+
+ endstops.validate_homing_move();
+
+ current_position.set(0.0, 0.0);
+
+ #if ENABLED(SENSORLESS_HOMING)
+ tmc_disable_stallguard(stepperX, stealth_states.x);
+ tmc_disable_stallguard(stepperY, stealth_states.y);
+ #if AXIS_HAS_STALLGUARD(X2)
+ tmc_disable_stallguard(stepperX2, stealth_states.x2);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Y2)
+ tmc_disable_stallguard(stepperY2, stealth_states.y2);
+ #endif
+ #endif
+ }
+
+#endif // QUICK_HOME
+
+#if ENABLED(Z_SAFE_HOMING)
+
+ inline void home_z_safely() {
+ DEBUG_SECTION(log_G28, "home_z_safely", DEBUGGING(LEVELING));
+
+ // Disallow Z homing if X or Y homing is needed
+ if (homing_needed_error(_BV(X_AXIS) | _BV(Y_AXIS))) return;
+
+ sync_plan_position();
+
+ /**
+ * Move the Z probe (or just the nozzle) to the safe homing point
+ * (Z is already at the right height)
+ */
+ constexpr xy_float_t safe_homing_xy = { Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT };
+ #if HAS_HOME_OFFSET
+ xy_float_t okay_homing_xy = safe_homing_xy;
+ okay_homing_xy -= home_offset;
+ #else
+ constexpr xy_float_t okay_homing_xy = safe_homing_xy;
+ #endif
+
+ destination.set(okay_homing_xy, current_position.z);
+
+ TERN_(HOMING_Z_WITH_PROBE, destination -= probe.offset_xy);
+
+ if (position_is_reachable(destination)) {
+
+ if (DEBUGGING(LEVELING)) DEBUG_POS("home_z_safely", destination);
+
+ // Free the active extruder for movement
+ TERN_(DUAL_X_CARRIAGE, idex_set_parked(false));
+
+ TERN_(SENSORLESS_HOMING, safe_delay(500)); // Short delay needed to settle
+
+ do_blocking_move_to_xy(destination);
+ homeaxis(Z_AXIS);
+ }
+ else {
+ LCD_MESSAGEPGM(MSG_ZPROBE_OUT);
+ SERIAL_ECHO_MSG(STR_ZPROBE_OUT_SER);
+ }
+ }
+
+#endif // Z_SAFE_HOMING
+
+#if ENABLED(IMPROVE_HOMING_RELIABILITY)
+
+ slow_homing_t begin_slow_homing() {
+ slow_homing_t slow_homing{0};
+ slow_homing.acceleration.set(planner.settings.max_acceleration_mm_per_s2[X_AXIS],
+ planner.settings.max_acceleration_mm_per_s2[Y_AXIS]);
+ planner.settings.max_acceleration_mm_per_s2[X_AXIS] = 100;
+ planner.settings.max_acceleration_mm_per_s2[Y_AXIS] = 100;
+ #if HAS_CLASSIC_JERK
+ slow_homing.jerk_xy = planner.max_jerk;
+ planner.max_jerk.set(0, 0);
+ #endif
+ planner.reset_acceleration_rates();
+ return slow_homing;
+ }
+
+ void end_slow_homing(const slow_homing_t &slow_homing) {
+ planner.settings.max_acceleration_mm_per_s2[X_AXIS] = slow_homing.acceleration.x;
+ planner.settings.max_acceleration_mm_per_s2[Y_AXIS] = slow_homing.acceleration.y;
+ TERN_(HAS_CLASSIC_JERK, planner.max_jerk = slow_homing.jerk_xy);
+ planner.reset_acceleration_rates();
+ }
+
+#endif // IMPROVE_HOMING_RELIABILITY
+
+/**
+ * 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.
+ *
+ * O Home only if position is unknown
+ *
+ * Rn Raise by n mm/inches before homing
+ *
+ * Cartesian/SCARA parameters
+ *
+ * X Home to the X endstop
+ * Y Home to the Y endstop
+ * Z Home to the Z endstop
+ */
+void GcodeSuite::G28() {
+ DEBUG_SECTION(log_G28, "G28", DEBUGGING(LEVELING));
+ if (DEBUGGING(LEVELING)) log_machine_info();
+
+ TERN_(LASER_MOVE_G28_OFF, cutter.set_inline_enabled(false)); // turn off laser
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ bool IDEX_saved_duplication_state = extruder_duplication_enabled;
+ DualXMode IDEX_saved_mode = dual_x_carriage_mode;
+ #endif
+
+ #if ENABLED(MARLIN_DEV_MODE)
+ if (parser.seen('S')) {
+ LOOP_XYZ(a) set_axis_is_at_home((AxisEnum)a);
+ sync_plan_position();
+ SERIAL_ECHOLNPGM("Simulated Homing");
+ report_current_position();
+ return;
+ }
+ #endif
+
+ // Home (O)nly if position is unknown
+ if (!axes_should_home() && parser.boolval('O')) {
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> homing not needed, skip");
+ return;
+ }
+
+ TERN_(DWIN_CREALITY_LCD, DWIN_StartHoming());
+ TERN_(EXTENSIBLE_UI, ExtUI::onHomingStart());
+
+ planner.synchronize(); // Wait for planner moves to finish!
+
+ SET_SOFT_ENDSTOP_LOOSE(false); // Reset a leftover 'loose' motion state
+
+ // Disable the leveling matrix before homing
+ #if HAS_LEVELING
+ const bool leveling_restore_state = parser.boolval('L', TERN(RESTORE_LEVELING_AFTER_G28, planner.leveling_active, ENABLED(ENABLE_LEVELING_AFTER_G28)));
+ IF_ENABLED(PROBE_MANUALLY, g29_in_progress = false); // Cancel the active G29 session
+ set_bed_leveling_enabled(false);
+ #endif
+
+ // Reset to the XY plane
+ TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY);
+
+ // Count this command as movement / activity
+ reset_stepper_timeout();
+
+ #define HAS_CURRENT_HOME(N) (defined(N##_CURRENT_HOME) && N##_CURRENT_HOME != N##_CURRENT)
+ #if HAS_CURRENT_HOME(X) || HAS_CURRENT_HOME(X2) || HAS_CURRENT_HOME(Y) || HAS_CURRENT_HOME(Y2)
+ #define HAS_HOMING_CURRENT 1
+ #endif
+
+ #if HAS_HOMING_CURRENT
+ auto debug_current = [](PGM_P const s, const int16_t a, const int16_t b){
+ serialprintPGM(s); DEBUG_ECHOLNPAIR(" current: ", a, " -> ", b);
+ };
+ #if HAS_CURRENT_HOME(X)
+ const int16_t tmc_save_current_X = stepperX.getMilliamps();
+ stepperX.rms_current(X_CURRENT_HOME);
+ if (DEBUGGING(LEVELING)) debug_current(PSTR("X"), tmc_save_current_X, X_CURRENT_HOME);
+ #endif
+ #if HAS_CURRENT_HOME(X2)
+ const int16_t tmc_save_current_X2 = stepperX2.getMilliamps();
+ stepperX2.rms_current(X2_CURRENT_HOME);
+ if (DEBUGGING(LEVELING)) debug_current(PSTR("X2"), tmc_save_current_X2, X2_CURRENT_HOME);
+ #endif
+ #if HAS_CURRENT_HOME(Y)
+ const int16_t tmc_save_current_Y = stepperY.getMilliamps();
+ stepperY.rms_current(Y_CURRENT_HOME);
+ if (DEBUGGING(LEVELING)) debug_current(PSTR("Y"), tmc_save_current_Y, Y_CURRENT_HOME);
+ #endif
+ #if HAS_CURRENT_HOME(Y2)
+ const int16_t tmc_save_current_Y2 = stepperY2.getMilliamps();
+ stepperY2.rms_current(Y2_CURRENT_HOME);
+ if (DEBUGGING(LEVELING)) debug_current(PSTR("Y2"), tmc_save_current_Y2, Y2_CURRENT_HOME);
+ #endif
+ #endif
+
+ TERN_(IMPROVE_HOMING_RELIABILITY, slow_homing_t slow_homing = begin_slow_homing());
+
+ // Always home with tool 0 active
+ #if HAS_MULTI_HOTEND
+ #if DISABLED(DELTA) || ENABLED(DELTA_HOME_TO_SAFE_ZONE)
+ const uint8_t old_tool_index = active_extruder;
+ #endif
+ // PARKING_EXTRUDER homing requires different handling of movement / solenoid activation, depending on the side of homing
+ #if ENABLED(PARKING_EXTRUDER)
+ const bool pe_final_change_must_unpark = parking_extruder_unpark_after_homing(old_tool_index, X_HOME_DIR + 1 == old_tool_index * 2);
+ #endif
+ tool_change(0, true);
+ #endif
+
+ TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false));
+
+ remember_feedrate_scaling_off();
+
+ endstops.enable(true); // Enable endstops for next homing move
+
+ #if ENABLED(DELTA)
+
+ constexpr bool doZ = true; // for NANODLP_Z_SYNC if your DLP is on a DELTA
+
+ home_delta();
+
+ TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(slow_homing));
+
+ #else // NOT DELTA
+
+ const bool homeZ = parser.seen('Z'),
+ needX = homeZ && TERN0(Z_SAFE_HOMING, axes_should_home(_BV(X_AXIS))),
+ needY = homeZ && TERN0(Z_SAFE_HOMING, axes_should_home(_BV(Y_AXIS))),
+ homeX = needX || parser.seen('X'), homeY = needY || parser.seen('Y'),
+ home_all = homeX == homeY && homeX == homeZ, // All or None
+ doX = home_all || homeX, doY = home_all || homeY, doZ = home_all || homeZ;
+
+ #if ENABLED(HOME_Z_FIRST)
+
+ if (doZ) homeaxis(Z_AXIS);
+
+ #endif
+
+ const float z_homing_height = TERN1(UNKNOWN_Z_NO_RAISE, axis_is_trusted(Z_AXIS))
+ ? (parser.seenval('R') ? parser.value_linear_units() : Z_HOMING_HEIGHT)
+ : 0;
+
+ if (z_homing_height && (doX || doY || TERN0(Z_SAFE_HOMING, doZ))) {
+ // Raise Z before homing any other axes and z is not already high enough (never lower z)
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Raise Z (before homing) by ", z_homing_height);
+ do_z_clearance(z_homing_height, axis_is_trusted(Z_AXIS), DISABLED(UNKNOWN_Z_NO_RAISE));
+ }
+
+ #if ENABLED(QUICK_HOME)
+
+ if (doX && doY) quick_home_xy();
+
+ #endif
+
+ // Home Y (before X)
+ if (ENABLED(HOME_Y_BEFORE_X) && (doY || TERN0(CODEPENDENT_XY_HOMING, doX)))
+ homeaxis(Y_AXIS);
+
+ // Home X
+ if (doX || (doY && ENABLED(CODEPENDENT_XY_HOMING) && DISABLED(HOME_Y_BEFORE_X))) {
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+
+ // Always home the 2nd (right) extruder first
+ active_extruder = 1;
+ homeaxis(X_AXIS);
+
+ // Remember this extruder's position for later tool change
+ inactive_extruder_x = current_position.x;
+
+ // Home the 1st (left) extruder
+ active_extruder = 0;
+ homeaxis(X_AXIS);
+
+ // Consider the active extruder to be in its "parked" position
+ idex_set_parked();
+
+ #else
+
+ homeaxis(X_AXIS);
+
+ #endif
+ }
+
+ // Home Y (after X)
+ if (DISABLED(HOME_Y_BEFORE_X) && doY)
+ homeaxis(Y_AXIS);
+
+ TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(slow_homing));
+
+ // Home Z last if homing towards the bed
+ #if DISABLED(HOME_Z_FIRST)
+ if (doZ) {
+ #if EITHER(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN)
+ stepper.set_all_z_lock(false);
+ stepper.set_separate_multi_axis(false);
+ #endif
+
+ TERN_(BLTOUCH, bltouch.init());
+ TERN(Z_SAFE_HOMING, home_z_safely(), homeaxis(Z_AXIS));
+ probe.move_z_after_homing();
+ }
+ #endif
+
+ sync_plan_position();
+
+ #endif // !DELTA (G28)
+
+ /**
+ * Preserve DXC mode across a G28 for IDEX printers in DXC_DUPLICATION_MODE.
+ * This is important because it lets a user use the LCD Panel to set an IDEX Duplication mode, and
+ * then print a standard GCode file that contains a single print that does a G28 and has no other
+ * IDEX specific commands in it.
+ */
+ #if ENABLED(DUAL_X_CARRIAGE)
+
+ if (idex_is_duplicating()) {
+
+ TERN_(IMPROVE_HOMING_RELIABILITY, slow_homing = begin_slow_homing());
+
+ // Always home the 2nd (right) extruder first
+ active_extruder = 1;
+ homeaxis(X_AXIS);
+
+ // Remember this extruder's position for later tool change
+ inactive_extruder_x = current_position.x;
+
+ // Home the 1st (left) extruder
+ active_extruder = 0;
+ homeaxis(X_AXIS);
+
+ // Consider the active extruder to be parked
+ idex_set_parked();
+
+ dual_x_carriage_mode = IDEX_saved_mode;
+ set_duplication_enabled(IDEX_saved_duplication_state);
+
+ TERN_(IMPROVE_HOMING_RELIABILITY, end_slow_homing(slow_homing));
+ }
+
+ #endif // DUAL_X_CARRIAGE
+
+ endstops.not_homing();
+
+ // Clear endstop state for polled stallGuard endstops
+ TERN_(SPI_ENDSTOPS, endstops.clear_endstop_state());
+
+ #if BOTH(DELTA, 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
+
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(leveling_restore_state));
+
+ restore_feedrate_and_scaling();
+
+ // Restore the active tool after homing
+ #if HAS_MULTI_HOTEND && (DISABLED(DELTA) || ENABLED(DELTA_HOME_TO_SAFE_ZONE))
+ tool_change(old_tool_index, TERN(PARKING_EXTRUDER, !pe_final_change_must_unpark, DISABLED(DUAL_X_CARRIAGE))); // Do move if one of these
+ #endif
+
+ #if HAS_HOMING_CURRENT
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Restore driver current...");
+ #if HAS_CURRENT_HOME(X)
+ stepperX.rms_current(tmc_save_current_X);
+ #endif
+ #if HAS_CURRENT_HOME(X2)
+ stepperX2.rms_current(tmc_save_current_X2);
+ #endif
+ #if HAS_CURRENT_HOME(Y)
+ stepperY.rms_current(tmc_save_current_Y);
+ #endif
+ #if HAS_CURRENT_HOME(Y2)
+ stepperY2.rms_current(tmc_save_current_Y2);
+ #endif
+ #endif
+
+ ui.refresh();
+
+ TERN_(DWIN_CREALITY_LCD, DWIN_CompletedHoming());
+ TERN_(EXTENSIBLE_UI, ExtUI::onHomingComplete());
+
+ report_current_position();
+
+ if (ENABLED(NANODLP_Z_SYNC) && (doZ || ENABLED(NANODLP_ALL_AXIS)))
+ SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP);
+
+ #if HAS_L64XX
+ // Set L6470 absolute position registers to counts
+ // constexpr *might* move this to PROGMEM.
+ // If not, this will need a PROGMEM directive and an accessor.
+ static constexpr AxisEnum L64XX_axis_xref[MAX_L64XX] = {
+ X_AXIS, Y_AXIS, Z_AXIS,
+ X_AXIS, Y_AXIS, Z_AXIS, Z_AXIS,
+ E_AXIS, E_AXIS, E_AXIS, E_AXIS, E_AXIS, E_AXIS
+ };
+ for (uint8_t j = 1; j <= L64XX::chain[0]; j++) {
+ const uint8_t cv = L64XX::chain[j];
+ L64xxManager.set_param((L64XX_axis_t)cv, L6470_ABS_POS, stepper.position(L64XX_axis_xref[cv]));
+ }
+ #endif
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(true));
+}
diff --git a/Marlin/src/gcode/calibrate/G33.cpp b/Marlin/src/gcode/calibrate/G33.cpp
new file mode 100644
index 0000000..77cc457
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/G33.cpp
@@ -0,0 +1,648 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(DELTA_AUTO_CALIBRATION)
+
+#include "../gcode.h"
+#include "../../module/delta.h"
+#include "../../module/motion.h"
+#include "../../module/stepper.h"
+#include "../../module/endstops.h"
+#include "../../lcd/marlinui.h"
+
+#if HAS_BED_PROBE
+ #include "../../module/probe.h"
+#endif
+
+#if HAS_MULTI_HOTEND
+ #include "../../module/tool_change.h"
+#endif
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+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; VAR<NPP+0.9999; VAR+=N)
+#define I_LOOP_CAL_PT(VAR, S, N) for (float VAR=S; VAR>CEN+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)
+
+TERN_(HAS_MULTI_HOTEND, const uint8_t old_tool_index = active_extruder);
+
+float lcd_probe_pt(const xy_pos_t &xy);
+
+void ac_home() {
+ endstops.enable(true);
+ home_delta();
+ endstops.not_homing();
+}
+
+void ac_setup(const bool reset_bed) {
+ TERN_(HAS_MULTI_HOTEND, tool_change(0, true));
+
+ planner.synchronize();
+ remember_feedrate_scaling_off();
+
+ #if HAS_LEVELING
+ if (reset_bed) reset_bed_level(); // After full calibration bed-level data is no longer valid
+ #endif
+}
+
+void ac_cleanup(TERN_(HAS_MULTI_HOTEND, const uint8_t old_tool_index)) {
+ TERN_(DELTA_HOME_TO_SAFE_ZONE, do_blocking_move_to_z(delta_clip_start_height));
+ TERN_(HAS_BED_PROBE, probe.stow());
+ restore_feedrate_and_scaling();
+ TERN_(HAS_MULTI_HOTEND, tool_change(old_tool_index, true));
+}
+
+void print_signed_float(PGM_P const prefix, const float &f) {
+ SERIAL_ECHOPGM(" ");
+ serialprintPGM(prefix);
+ SERIAL_CHAR(':');
+ if (f >= 0) SERIAL_CHAR('+');
+ SERIAL_ECHO_F(f, 2);
+}
+
+/**
+ * - Print the delta settings
+ */
+static void print_calibration_settings(const bool end_stops, const bool tower_angles) {
+ SERIAL_ECHOPAIR(".Height:", delta_height);
+ if (end_stops) {
+ print_signed_float(PSTR("Ex"), delta_endstop_adj.a);
+ print_signed_float(PSTR("Ey"), delta_endstop_adj.b);
+ print_signed_float(PSTR("Ez"), delta_endstop_adj.c);
+ }
+ if (end_stops && tower_angles) {
+ SERIAL_ECHOPAIR(" Radius:", delta_radius);
+ SERIAL_EOL();
+ SERIAL_CHAR('.');
+ SERIAL_ECHO_SP(13);
+ }
+ if (tower_angles) {
+ print_signed_float(PSTR("Tx"), delta_tower_angle_trim.a);
+ print_signed_float(PSTR("Ty"), delta_tower_angle_trim.b);
+ print_signed_float(PSTR("Tz"), delta_tower_angle_trim.c);
+ }
+ if ((!end_stops && tower_angles) || (end_stops && !tower_angles)) { // XOR
+ SERIAL_ECHOPAIR(" Radius:", delta_radius);
+ }
+ SERIAL_EOL();
+}
+
+/**
+ * - Print the probe results
+ */
+static void print_calibration_results(const float z_pt[NPP + 1], const bool tower_points, const bool opposite_points) {
+ SERIAL_ECHOPGM(". ");
+ print_signed_float(PSTR("c"), z_pt[CEN]);
+ if (tower_points) {
+ print_signed_float(PSTR(" x"), z_pt[__A]);
+ print_signed_float(PSTR(" y"), z_pt[__B]);
+ print_signed_float(PSTR(" z"), z_pt[__C]);
+ }
+ if (tower_points && opposite_points) {
+ SERIAL_EOL();
+ SERIAL_CHAR('.');
+ SERIAL_ECHO_SP(13);
+ }
+ if (opposite_points) {
+ print_signed_float(PSTR("yz"), z_pt[_BC]);
+ print_signed_float(PSTR("zx"), z_pt[_CA]);
+ print_signed_float(PSTR("xy"), z_pt[_AB]);
+ }
+ SERIAL_EOL();
+}
+
+/**
+ * - Calculate the standard deviation from the zero plane
+ */
+static float std_dev_points(float z_pt[NPP + 1], const bool _0p_cal, const bool _1p_cal, const bool _4p_cal, const bool _4p_opp) {
+ if (!_0p_cal) {
+ float S2 = sq(z_pt[CEN]);
+ int16_t N = 1;
+ if (!_1p_cal) { // std dev from zero plane
+ LOOP_CAL_ACT(rad, _4p_cal, _4p_opp) {
+ S2 += sq(z_pt[rad]);
+ N++;
+ }
+ return LROUND(SQRT(S2 / N) * 1000.0f) / 1000.0f + 0.00001f;
+ }
+ }
+ return 0.00001f;
+}
+
+/**
+ * - Probe a point
+ */
+static float calibration_probe(const xy_pos_t &xy, const bool stow) {
+ #if HAS_BED_PROBE
+ return probe.probe_at_point(xy, stow ? PROBE_PT_STOW : PROBE_PT_RAISE, 0, true, false);
+ #else
+ UNUSED(stow);
+ return lcd_probe_pt(xy);
+ #endif
+}
+
+/**
+ * - Probe a grid
+ */
+static bool probe_calibration_points(float z_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 || probe_points == -1,
+ _4p_calibration = probe_points == 2,
+ _4p_opposite_points = _4p_calibration && !towers_set,
+ _7p_calibration = probe_points >= 3,
+ _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_center = probe_points >= 5 && probe_points <= 7,
+ _7p_9_center = probe_points >= 8;
+
+ LOOP_CAL_ALL(rad) z_pt[rad] = 0.0f;
+
+ if (!_0p_calibration) {
+
+ const float dcr = delta_calibration_radius();
+
+ if (!_7p_no_intermediates && !_7p_4_intermediates && !_7p_11_intermediates) { // probe the center
+ const xy_pos_t center{0};
+ z_pt[CEN] += calibration_probe(center, stow_after_each);
+ if (isnan(z_pt[CEN])) return false;
+ }
+
+ if (_7p_calibration) { // probe extra center points
+ const float start = _7p_9_center ? float(_CA) + _7P_STEP / 3.0f : _7p_6_center ? float(_CA) : float(__C),
+ steps = _7p_9_center ? _4P_STEP / 3.0f : _7p_6_center ? _7P_STEP : _4P_STEP;
+ I_LOOP_CAL_PT(rad, start, steps) {
+ const float a = RADIANS(210 + (360 / NPP) * (rad - 1)),
+ r = dcr * 0.1;
+ const xy_pos_t vec = { cos(a), sin(a) };
+ z_pt[CEN] += calibration_probe(vec * r, stow_after_each);
+ if (isnan(z_pt[CEN])) return false;
+ }
+ z_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.0f : // 15r * 6 + 10c = 100
+ _7p_11_intermediates ? _7P_STEP / 12.0f : // 12r * 6 + 9c = 81
+ _7p_8_intermediates ? _7P_STEP / 9.0f : // 9r * 6 + 10c = 64
+ _7p_6_intermediates ? _7P_STEP / 7.0f : // 7r * 6 + 7c = 49
+ _7p_4_intermediates ? _7P_STEP / 5.0f : // 5r * 6 + 6c = 36
+ _7p_2_intermediates ? _7P_STEP / 3.0f : // 3r * 6 + 7c = 25
+ _7p_1_intermediates ? _7P_STEP / 2.0f : // 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(rad, start, _7p_9_center ? steps * 3 : steps) {
+ const int8_t offset = _7p_9_center ? 2 : 0;
+ for (int8_t circle = 0; circle <= offset; circle++) {
+ const float a = RADIANS(210 + (360 / NPP) * (rad - 1)),
+ r = dcr * (1 - 0.1 * (zig_zag ? offset - circle : circle)),
+ interpol = FMOD(rad, 1);
+ const xy_pos_t vec = { cos(a), sin(a) };
+ const float z_temp = calibration_probe(vec * r, stow_after_each);
+ if (isnan(z_temp)) return false;
+ // split probe point to neighbouring calibration points
+ z_pt[uint8_t(LROUND(rad - interpol + NPP - 1)) % NPP + 1] += z_temp * sq(cos(RADIANS(interpol * 90)));
+ z_pt[uint8_t(LROUND(rad - interpol)) % NPP + 1] += z_temp * sq(sin(RADIANS(interpol * 90)));
+ }
+ zig_zag = !zig_zag;
+ }
+ if (_7p_intermed_points)
+ LOOP_CAL_RAD(rad)
+ z_pt[rad] /= _7P_STEP / steps;
+
+ do_blocking_move_to_xy(0.0f, 0.0f);
+ }
+ }
+ return true;
+}
+
+/**
+ * kinematics routines and auto tune matrix scaling parameters:
+ * see https://github.com/LVD-AC/Marlin-AC/tree/1.1.x-AC/documentation for
+ * - formulae for approximative forward kinematics in the end-stop displacement matrix
+ * - definition of the matrix scaling parameters
+ */
+static void reverse_kinematics_probe_points(float z_pt[NPP + 1], abc_float_t mm_at_pt_axis[NPP + 1]) {
+ xyz_pos_t pos{0};
+
+ const float dcr = delta_calibration_radius();
+ LOOP_CAL_ALL(rad) {
+ const float a = RADIANS(210 + (360 / NPP) * (rad - 1)),
+ r = (rad == CEN ? 0.0f : dcr);
+ pos.set(cos(a) * r, sin(a) * r, z_pt[rad]);
+ inverse_kinematics(pos);
+ mm_at_pt_axis[rad] = delta;
+ }
+}
+
+static void forward_kinematics_probe_points(abc_float_t mm_at_pt_axis[NPP + 1], float z_pt[NPP + 1]) {
+ const float r_quot = delta_calibration_radius() / delta_radius;
+
+ #define ZPP(N,I,A) (((1.0f + r_quot * (N)) / 3.0f) * mm_at_pt_axis[I].A)
+ #define Z00(I, A) ZPP( 0, I, A)
+ #define Zp1(I, A) ZPP(+1, I, A)
+ #define Zm1(I, A) ZPP(-1, I, A)
+ #define Zp2(I, A) ZPP(+2, I, A)
+ #define Zm2(I, A) ZPP(-2, I, A)
+
+ z_pt[CEN] = Z00(CEN, a) + Z00(CEN, b) + Z00(CEN, c);
+ z_pt[__A] = Zp2(__A, a) + Zm1(__A, b) + Zm1(__A, c);
+ z_pt[__B] = Zm1(__B, a) + Zp2(__B, b) + Zm1(__B, c);
+ z_pt[__C] = Zm1(__C, a) + Zm1(__C, b) + Zp2(__C, c);
+ z_pt[_BC] = Zm2(_BC, a) + Zp1(_BC, b) + Zp1(_BC, c);
+ z_pt[_CA] = Zp1(_CA, a) + Zm2(_CA, b) + Zp1(_CA, c);
+ z_pt[_AB] = Zp1(_AB, a) + Zp1(_AB, b) + Zm2(_AB, c);
+}
+
+static void calc_kinematics_diff_probe_points(float z_pt[NPP + 1], abc_float_t delta_e, const float delta_r, abc_float_t delta_t) {
+ const float z_center = z_pt[CEN];
+ abc_float_t diff_mm_at_pt_axis[NPP + 1], new_mm_at_pt_axis[NPP + 1];
+
+ reverse_kinematics_probe_points(z_pt, diff_mm_at_pt_axis);
+
+ delta_radius += delta_r;
+ delta_tower_angle_trim += delta_t;
+ recalc_delta_settings();
+ reverse_kinematics_probe_points(z_pt, new_mm_at_pt_axis);
+
+ LOOP_CAL_ALL(rad) diff_mm_at_pt_axis[rad] -= new_mm_at_pt_axis[rad] + delta_e;
+ forward_kinematics_probe_points(diff_mm_at_pt_axis, z_pt);
+
+ LOOP_CAL_RAD(rad) z_pt[rad] -= z_pt[CEN] - z_center;
+ z_pt[CEN] = z_center;
+
+ delta_radius -= delta_r;
+ delta_tower_angle_trim -= delta_t;
+ recalc_delta_settings();
+}
+
+static float auto_tune_h() {
+ const float r_quot = delta_calibration_radius() / delta_radius;
+ return RECIPROCAL(r_quot / (2.0f / 3.0f)); // (2/3)/CR
+}
+
+static float auto_tune_r() {
+ constexpr float diff = 0.01f, delta_r = diff;
+ float r_fac = 0.0f, z_pt[NPP + 1] = { 0.0f };
+ abc_float_t delta_e = { 0.0f }, delta_t = { 0.0f };
+
+ calc_kinematics_diff_probe_points(z_pt, delta_e, delta_r, delta_t);
+ r_fac = -(z_pt[__A] + z_pt[__B] + z_pt[__C] + z_pt[_BC] + z_pt[_CA] + z_pt[_AB]) / 6.0f;
+ r_fac = diff / r_fac / 3.0f; // 1/(3*delta_Z)
+ return r_fac;
+}
+
+static float auto_tune_a() {
+ constexpr float diff = 0.01f, delta_r = 0.0f;
+ float a_fac = 0.0f, z_pt[NPP + 1] = { 0.0f };
+ abc_float_t delta_e = { 0.0f }, delta_t = { 0.0f };
+
+ delta_t.reset();
+ LOOP_XYZ(axis) {
+ delta_t[axis] = diff;
+ calc_kinematics_diff_probe_points(z_pt, delta_e, delta_r, delta_t);
+ delta_t[axis] = 0;
+ a_fac += z_pt[uint8_t((axis * _4P_STEP) - _7P_STEP + NPP) % NPP + 1] / 6.0f;
+ a_fac -= z_pt[uint8_t((axis * _4P_STEP) + 1 + _7P_STEP)] / 6.0f;
+ }
+ a_fac = diff / a_fac / 3.0f; // 1/(3*delta_Z)
+ return a_fac;
+}
+
+/**
+ * G33 - Delta '1-4-7-point' Auto-Calibration
+ * Calibrate height, z_offset, endstops, delta radius, and tower angles.
+ *
+ * Parameters:
+ *
+ * Pn Number of probe points:
+ * P0 Normalizes calibration.
+ * P1 Calibrates height only with center probe.
+ * P2 Probe center and towers. Calibrate height, endstops and delta radius.
+ * P3 Probe all positions: center, towers and opposite towers. Calibrate 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
+ *
+ * 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
+ */
+void GcodeSuite::G33() {
+
+ const int8_t probe_points = parser.intval('P', DELTA_CALIBRATION_DEFAULT_POINTS);
+ if (!WITHIN(probe_points, 0, 10)) {
+ SERIAL_ECHOLNPGM("?(P)oints implausible (0-10).");
+ return;
+ }
+
+ const bool towers_set = !parser.seen('T');
+
+ const float calibration_precision = parser.floatval('C', 0.0f);
+ if (calibration_precision < 0) {
+ SERIAL_ECHOLNPGM("?(C)alibration precision implausible (>=0).");
+ return;
+ }
+
+ const int8_t force_iterations = parser.intval('F', 0);
+ if (!WITHIN(force_iterations, 0, 30)) {
+ SERIAL_ECHOLNPGM("?(F)orce iteration implausible (0-30).");
+ return;
+ }
+
+ const int8_t verbose_level = parser.byteval('V', 1);
+ if (!WITHIN(verbose_level, 0, 3)) {
+ SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-3).");
+ return;
+ }
+
+ const bool stow_after_each = parser.seen('E');
+
+ const bool _0p_calibration = probe_points == 0,
+ _1p_calibration = probe_points == 1 || probe_points == -1,
+ _4p_calibration = probe_points == 2,
+ _4p_opposite_points = _4p_calibration && !towers_set,
+ _7p_9_center = probe_points >= 8,
+ _tower_results = (_4p_calibration && towers_set) || probe_points >= 3,
+ _opposite_results = (_4p_calibration && !towers_set) || probe_points >= 3,
+ _endstop_results = probe_points != 1 && probe_points != -1 && probe_points != 0,
+ _angle_results = probe_points >= 3 && towers_set;
+ int8_t iterations = 0;
+ float test_precision,
+ zero_std_dev = (verbose_level ? 999.0f : 0.0f), // 0.0 in dry-run mode : forced end
+ zero_std_dev_min = zero_std_dev,
+ zero_std_dev_old = zero_std_dev,
+ h_factor, r_factor, a_factor,
+ r_old = delta_radius,
+ h_old = delta_height;
+
+ abc_pos_t e_old = delta_endstop_adj, a_old = delta_tower_angle_trim;
+
+ SERIAL_ECHOLNPGM("G33 Auto Calibrate");
+
+ const float dcr = delta_calibration_radius();
+
+ 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));
+ if (!position_is_reachable(cos(a) * dcr, sin(a) * dcr)) {
+ SERIAL_ECHOLNPGM("?Bed calibration radius implausible.");
+ return;
+ }
+ }
+ }
+
+ // Report settings
+ PGM_P const checkingac = PSTR("Checking... AC");
+ serialprintPGM(checkingac);
+ if (verbose_level == 0) SERIAL_ECHOPGM(" (DRY-RUN)");
+ SERIAL_EOL();
+ ui.set_status_P(checkingac);
+
+ print_calibration_settings(_endstop_results, _angle_results);
+
+ ac_setup(!_0p_calibration && !_1p_calibration);
+
+ if (!_0p_calibration) ac_home();
+
+ do { // start iterations
+
+ float z_at_pt[NPP + 1] = { 0.0f };
+
+ test_precision = zero_std_dev_old != 999.0f ? (zero_std_dev + zero_std_dev_old) / 2.0f : zero_std_dev;
+ iterations++;
+
+ // Probe the points
+ zero_std_dev_old = zero_std_dev;
+ if (!probe_calibration_points(z_at_pt, probe_points, towers_set, stow_after_each)) {
+ SERIAL_ECHOLNPGM("Correct delta settings with M665 and M666");
+ return ac_cleanup(TERN_(HAS_MULTI_HOTEND, old_tool_index));
+ }
+ zero_std_dev = std_dev_points(z_at_pt, _0p_calibration, _1p_calibration, _4p_calibration, _4p_opposite_points);
+
+ // Solve matrices
+
+ if ((zero_std_dev < test_precision || iterations <= force_iterations) && zero_std_dev > calibration_precision) {
+
+ #if !HAS_BED_PROBE
+ test_precision = 0.0f; // forced end
+ #endif
+
+ if (zero_std_dev < zero_std_dev_min) {
+ // set roll-back point
+ e_old = delta_endstop_adj;
+ r_old = delta_radius;
+ h_old = delta_height;
+ a_old = delta_tower_angle_trim;
+ }
+
+ abc_float_t e_delta = { 0.0f }, t_delta = { 0.0f };
+ float r_delta = 0.0f;
+
+ /**
+ * convergence matrices:
+ * see https://github.com/LVD-AC/Marlin-AC/tree/1.1.x-AC/documentation for
+ * - definition of the matrix scaling parameters
+ * - matrices for 4 and 7 point calibration
+ */
+ #define ZP(N,I) ((N) * z_at_pt[I] / 4.0f) // 4.0 = divider to normalize to integers
+ #define Z12(I) ZP(12, I)
+ #define Z4(I) ZP(4, I)
+ #define Z2(I) ZP(2, I)
+ #define Z1(I) ZP(1, I)
+ #define Z0(I) ZP(0, I)
+
+ // calculate factors
+ if (_7p_9_center) calibration_radius_factor = 0.9f;
+ h_factor = auto_tune_h();
+ r_factor = auto_tune_r();
+ a_factor = auto_tune_a();
+ calibration_radius_factor = 1.0f;
+
+ switch (probe_points) {
+ case 0:
+ test_precision = 0.0f; // forced end
+ break;
+
+ case 1:
+ test_precision = 0.0f; // forced end
+ LOOP_XYZ(axis) e_delta[axis] = +Z4(CEN);
+ break;
+
+ case 2:
+ if (towers_set) { // see 4 point calibration (towers) matrix
+ e_delta.set((+Z4(__A) -Z2(__B) -Z2(__C)) * h_factor +Z4(CEN),
+ (-Z2(__A) +Z4(__B) -Z2(__C)) * h_factor +Z4(CEN),
+ (-Z2(__A) -Z2(__B) +Z4(__C)) * h_factor +Z4(CEN));
+ r_delta = (+Z4(__A) +Z4(__B) +Z4(__C) -Z12(CEN)) * r_factor;
+ }
+ else { // see 4 point calibration (opposites) matrix
+ e_delta.set((-Z4(_BC) +Z2(_CA) +Z2(_AB)) * h_factor +Z4(CEN),
+ (+Z2(_BC) -Z4(_CA) +Z2(_AB)) * h_factor +Z4(CEN),
+ (+Z2(_BC) +Z2(_CA) -Z4(_AB)) * h_factor +Z4(CEN));
+ r_delta = (+Z4(_BC) +Z4(_CA) +Z4(_AB) -Z12(CEN)) * r_factor;
+ }
+ break;
+
+ default: // see 7 point calibration (towers & opposites) matrix
+ e_delta.set((+Z2(__A) -Z1(__B) -Z1(__C) -Z2(_BC) +Z1(_CA) +Z1(_AB)) * h_factor +Z4(CEN),
+ (-Z1(__A) +Z2(__B) -Z1(__C) +Z1(_BC) -Z2(_CA) +Z1(_AB)) * h_factor +Z4(CEN),
+ (-Z1(__A) -Z1(__B) +Z2(__C) +Z1(_BC) +Z1(_CA) -Z2(_AB)) * h_factor +Z4(CEN));
+ r_delta = (+Z2(__A) +Z2(__B) +Z2(__C) +Z2(_BC) +Z2(_CA) +Z2(_AB) -Z12(CEN)) * r_factor;
+
+ if (towers_set) { // see 7 point tower angle calibration (towers & opposites) matrix
+ t_delta.set((+Z0(__A) -Z4(__B) +Z4(__C) +Z0(_BC) -Z4(_CA) +Z4(_AB) +Z0(CEN)) * a_factor,
+ (+Z4(__A) +Z0(__B) -Z4(__C) +Z4(_BC) +Z0(_CA) -Z4(_AB) +Z0(CEN)) * a_factor,
+ (-Z4(__A) +Z4(__B) +Z0(__C) -Z4(_BC) +Z4(_CA) +Z0(_AB) +Z0(CEN)) * a_factor);
+ }
+ break;
+ }
+ delta_endstop_adj += e_delta;
+ delta_radius += r_delta;
+ delta_tower_angle_trim += t_delta;
+ }
+ else if (zero_std_dev >= test_precision) {
+ // roll back
+ delta_endstop_adj = e_old;
+ delta_radius = r_old;
+ delta_height = h_old;
+ delta_tower_angle_trim = a_old;
+ }
+
+ if (verbose_level != 0) { // !dry run
+
+ // Normalize angles to least-squares
+ if (_angle_results) {
+ float a_sum = 0.0f;
+ LOOP_XYZ(axis) a_sum += delta_tower_angle_trim[axis];
+ LOOP_XYZ(axis) delta_tower_angle_trim[axis] -= a_sum / 3.0f;
+ }
+
+ // adjust delta_height and endstops by the max amount
+ const float z_temp = _MAX(delta_endstop_adj.a, delta_endstop_adj.b, delta_endstop_adj.c);
+ 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 == 3)
+ print_calibration_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_ECHOPGM("Calibration OK");
+ SERIAL_ECHO_SP(32);
+ #if HAS_BED_PROBE
+ if (zero_std_dev >= test_precision && !_1p_calibration && !_0p_calibration)
+ SERIAL_ECHOPGM("rolling back.");
+ else
+ #endif
+ {
+ SERIAL_ECHOPAIR_F("std dev:", 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)LROUND(zero_std_dev_min * 1000.0f));
+ else
+ sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev_min));
+ ui.set_status(mess);
+ print_calibration_settings(_endstop_results, _angle_results);
+ SERIAL_ECHOLNPGM("Save with M500 and/or copy to Configuration.h");
+ }
+ else { // !end iterations
+ char mess[15];
+ if (iterations < 31)
+ sprintf_P(mess, PSTR("Iteration : %02i"), (unsigned int)iterations);
+ else
+ strcpy_P(mess, PSTR("No convergence"));
+ SERIAL_ECHO(mess);
+ SERIAL_ECHO_SP(32);
+ SERIAL_ECHOLNPAIR_F("std dev:", zero_std_dev, 3);
+ ui.set_status(mess);
+ if (verbose_level > 1)
+ print_calibration_settings(_endstop_results, _angle_results);
+ }
+ }
+ else { // dry run
+ PGM_P const enddryrun = PSTR("End DRY-RUN");
+ serialprintPGM(enddryrun);
+ SERIAL_ECHO_SP(35);
+ SERIAL_ECHOLNPAIR_F("std dev:", zero_std_dev, 3);
+
+ 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)LROUND(zero_std_dev * 1000.0f));
+ else
+ sprintf_P(&mess[15], PSTR("%03i.x"), (int)LROUND(zero_std_dev));
+ ui.set_status(mess);
+ }
+ ac_home();
+ }
+ while (((zero_std_dev < test_precision && iterations < 31) || iterations <= force_iterations) && zero_std_dev > calibration_precision);
+
+ ac_cleanup(TERN_(HAS_MULTI_HOTEND, old_tool_index));
+}
+
+#endif // DELTA_AUTO_CALIBRATION
diff --git a/Marlin/src/gcode/calibrate/G34.cpp b/Marlin/src/gcode/calibrate/G34.cpp
new file mode 100644
index 0000000..bcca00d
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/G34.cpp
@@ -0,0 +1,157 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(MECHANICAL_GANTRY_CALIBRATION)
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/stepper.h"
+#include "../../module/endstops.h"
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
+#include "../../core/debug_out.h"
+
+void GcodeSuite::G34() {
+
+ // Home before the alignment procedure
+ if (!all_axes_trusted()) home_all_axes();
+
+ TERN_(HAS_LEVELING, TEMPORARY_BED_LEVELING_STATE(false));
+
+ SET_SOFT_ENDSTOP_LOOSE(true);
+ TemporaryGlobalEndstopsState unlock_z(false);
+
+ #ifdef GANTRY_CALIBRATION_COMMANDS_PRE
+ gcode.process_subcommands_now_P(PSTR(GANTRY_CALIBRATION_COMMANDS_PRE));
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Sub Commands Processed");
+ #endif
+
+ #ifdef GANTRY_CALIBRATION_SAFE_POSITION
+ // Move XY to safe position
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Parking XY");
+ const xy_pos_t safe_pos = GANTRY_CALIBRATION_SAFE_POSITION;
+ do_blocking_move_to(safe_pos, MMM_TO_MMS(GANTRY_CALIBRATION_XY_PARK_FEEDRATE));
+ #endif
+
+ const float move_distance = parser.intval('Z', GANTRY_CALIBRATION_EXTRA_HEIGHT),
+ zbase = ENABLED(GANTRY_CALIBRATION_TO_MIN) ? Z_MIN_POS : Z_MAX_POS,
+ zpounce = zbase - move_distance, zgrind = zbase + move_distance;
+
+ // Move Z to pounce position
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Setting Z Pounce");
+ do_blocking_move_to_z(zpounce, homing_feedrate(Z_AXIS));
+
+ // Store current motor settings, then apply reduced value
+
+ #define _REDUCE_CURRENT ANY(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_PWM, HAS_MOTOR_CURRENT_DAC, HAS_MOTOR_CURRENT_I2C, HAS_TRINAMIC_CONFIG)
+ #if _REDUCE_CURRENT
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Reducing Current");
+ #endif
+
+ #if HAS_MOTOR_CURRENT_SPI
+ const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT);
+ const uint32_t previous_current = stepper.motor_current_setting[Z_AXIS];
+ stepper.set_digipot_current(Z_AXIS, target_current);
+ #elif HAS_MOTOR_CURRENT_PWM
+ const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT);
+ const uint32_t previous_current = stepper.motor_current_setting[Z_AXIS];
+ stepper.set_digipot_current(1, target_current);
+ #elif ENABLED(HAS_MOTOR_CURRENT_DAC)
+ const float target_current = parser.floatval('S', GANTRY_CALIBRATION_CURRENT);
+ const float previous_current = dac_amps(Z_AXIS, target_current);
+ stepper_dac.set_current_value(Z_AXIS, target_current);
+ #elif ENABLED(HAS_MOTOR_CURRENT_I2C)
+ const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT);
+ previous_current = dac_amps(Z_AXIS);
+ digipot_i2c.set_current(Z_AXIS, target_current)
+ #elif HAS_TRINAMIC_CONFIG
+ const uint16_t target_current = parser.intval('S', GANTRY_CALIBRATION_CURRENT);
+ static uint16_t previous_current_arr[NUM_Z_STEPPER_DRIVERS];
+ #if AXIS_IS_TMC(Z)
+ previous_current_arr[0] = stepperZ.getMilliamps();
+ stepperZ.rms_current(target_current);
+ #endif
+ #if AXIS_IS_TMC(Z2)
+ previous_current_arr[1] = stepperZ2.getMilliamps();
+ stepperZ2.rms_current(target_current);
+ #endif
+ #if AXIS_IS_TMC(Z3)
+ previous_current_arr[2] = stepperZ3.getMilliamps();
+ stepperZ3.rms_current(target_current);
+ #endif
+ #if AXIS_IS_TMC(Z4)
+ previous_current_arr[3] = stepperZ4.getMilliamps();
+ stepperZ4.rms_current(target_current);
+ #endif
+ #endif
+
+ // Do Final Z move to adjust
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Final Z Move");
+ do_blocking_move_to_z(zgrind, MMM_TO_MMS(GANTRY_CALIBRATION_FEEDRATE));
+
+ // Back off end plate, back to normal motion range
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Z Backoff");
+ do_blocking_move_to_z(zpounce, MMM_TO_MMS(GANTRY_CALIBRATION_FEEDRATE));
+
+ #if _REDUCE_CURRENT
+ // Reset current to original values
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Restore Current");
+ #endif
+
+ #if HAS_MOTOR_CURRENT_SPI
+ stepper.set_digipot_current(Z_AXIS, previous_current);
+ #elif HAS_MOTOR_CURRENT_PWM
+ stepper.set_digipot_current(1, previous_current);
+ #elif ENABLED(HAS_MOTOR_CURRENT_DAC)
+ stepper_dac.set_current_value(Z_AXIS, previous_current);
+ #elif ENABLED(HAS_MOTOR_CURRENT_I2C)
+ digipot_i2c.set_current(Z_AXIS, previous_current)
+ #elif HAS_TRINAMIC_CONFIG
+ #if AXIS_IS_TMC(Z)
+ stepperZ.rms_current(previous_current_arr[0]);
+ #endif
+ #if AXIS_IS_TMC(Z2)
+ stepperZ2.rms_current(previous_current_arr[1]);
+ #endif
+ #if AXIS_IS_TMC(Z3)
+ stepperZ3.rms_current(previous_current_arr[2]);
+ #endif
+ #if AXIS_IS_TMC(Z4)
+ stepperZ4.rms_current(previous_current_arr[3]);
+ #endif
+ #endif
+
+ #ifdef GANTRY_CALIBRATION_COMMANDS_POST
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Running Post Commands");
+ gcode.process_subcommands_now_P(PSTR(GANTRY_CALIBRATION_COMMANDS_POST));
+ #endif
+
+ SET_SOFT_ENDSTOP_LOOSE(false);
+}
+
+#endif // MECHANICAL_GANTRY_CALIBRATION
diff --git a/Marlin/src/gcode/calibrate/G34_M422.cpp b/Marlin/src/gcode/calibrate/G34_M422.cpp
new file mode 100644
index 0000000..0bcf954
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/G34_M422.cpp
@@ -0,0 +1,533 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if EITHER(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN)
+
+#include "../../feature/z_stepper_align.h"
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/stepper.h"
+#include "../../module/planner.h"
+#include "../../module/probe.h"
+#include "../../lcd/marlinui.h" // for LCD_MESSAGEPGM
+
+#if HAS_LEVELING
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#if HAS_MULTI_HOTEND
+ #include "../../module/tool_change.h"
+#endif
+
+#if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ #include "../../libs/least_squares_fit.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
+#include "../../core/debug_out.h"
+
+/**
+ * G34: Z-Stepper automatic alignment
+ *
+ * Manual stepper lock controls (reset by G28):
+ * L Unlock all steppers
+ * Z<1-4> Z stepper to lock / unlock
+ * S<state> 0=UNLOCKED 1=LOCKED. If omitted, assume LOCKED.
+ *
+ * Examples:
+ * G34 Z1 ; Lock Z1
+ * G34 L Z2 ; Unlock all, then lock Z2
+ * G34 Z2 S0 ; Unlock Z2
+ *
+ * With Z_STEPPER_AUTO_ALIGN:
+ * I<iterations> Number of tests. If omitted, Z_STEPPER_ALIGN_ITERATIONS.
+ * T<accuracy> Target Accuracy factor. If omitted, Z_STEPPER_ALIGN_ACC.
+ * A<amplification> Provide an Amplification value. If omitted, Z_STEPPER_ALIGN_AMP.
+ * R Flag to recalculate points based on current probe offsets
+ */
+void GcodeSuite::G34() {
+ DEBUG_SECTION(log_G34, "G34", DEBUGGING(LEVELING));
+ if (DEBUGGING(LEVELING)) log_machine_info();
+
+ planner.synchronize(); // Prevent damage
+
+ const bool seenL = parser.seen('L');
+ if (seenL) stepper.set_all_z_lock(false);
+
+ const bool seenZ = parser.seenval('Z');
+ if (seenZ) {
+ const bool state = parser.boolval('S', true);
+ switch (parser.intval('Z')) {
+ case 1: stepper.set_z1_lock(state); break;
+ case 2: stepper.set_z2_lock(state); break;
+ #if NUM_Z_STEPPER_DRIVERS >= 3
+ case 3: stepper.set_z3_lock(state); break;
+ #if NUM_Z_STEPPER_DRIVERS >= 4
+ case 4: stepper.set_z4_lock(state); break;
+ #endif
+ #endif
+ }
+ }
+
+ if (seenL || seenZ) {
+ stepper.set_separate_multi_axis(seenZ);
+ return;
+ }
+
+ #if ENABLED(Z_STEPPER_AUTO_ALIGN)
+ do { // break out on error
+
+ #if NUM_Z_STEPPER_DRIVERS == 4
+ SERIAL_ECHOLNPGM("Alignment for 4 steppers is Experimental!");
+ #elif NUM_Z_STEPPER_DRIVERS > 4
+ SERIAL_ECHOLNPGM("Alignment not supported for over 4 steppers");
+ break;
+ #endif
+
+ const int8_t z_auto_align_iterations = parser.intval('I', Z_STEPPER_ALIGN_ITERATIONS);
+ if (!WITHIN(z_auto_align_iterations, 1, 30)) {
+ SERIAL_ECHOLNPGM("?(I)teration out of bounds (1-30).");
+ break;
+ }
+
+ const float z_auto_align_accuracy = parser.floatval('T', Z_STEPPER_ALIGN_ACC);
+ if (!WITHIN(z_auto_align_accuracy, 0.01f, 1.0f)) {
+ SERIAL_ECHOLNPGM("?(T)arget accuracy out of bounds (0.01-1.0).");
+ break;
+ }
+
+ const float z_auto_align_amplification = TERN(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS, Z_STEPPER_ALIGN_AMP, parser.floatval('A', Z_STEPPER_ALIGN_AMP));
+ if (!WITHIN(ABS(z_auto_align_amplification), 0.5f, 2.0f)) {
+ SERIAL_ECHOLNPGM("?(A)mplification out of bounds (0.5-2.0).");
+ break;
+ }
+
+ if (parser.seen('R')) z_stepper_align.reset_to_default();
+
+ const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE;
+
+ // Disable the leveling matrix before auto-aligning
+ #if HAS_LEVELING
+ TERN_(RESTORE_LEVELING_AFTER_G34, const bool leveling_was_active = planner.leveling_active);
+ set_bed_leveling_enabled(false);
+ #endif
+
+ TERN_(CNC_WORKSPACE_PLANES, workspace_plane = PLANE_XY);
+
+ // Always home with tool 0 active
+ #if HAS_MULTI_HOTEND
+ const uint8_t old_tool_index = active_extruder;
+ tool_change(0, true);
+ #endif
+
+ TERN_(HAS_DUPLICATION_MODE, set_duplication_enabled(false));
+
+ // In BLTOUCH HS mode, the probe travels in a deployed state.
+ // Users of G34 might have a badly misaligned bed, so raise Z by the
+ // length of the deployed pin (BLTOUCH stroke < 7mm)
+ #define Z_BASIC_CLEARANCE (Z_CLEARANCE_BETWEEN_PROBES + 7.0f * BOTH(BLTOUCH, BLTOUCH_HS_MODE))
+
+ // Compute a worst-case clearance height to probe from. After the first
+ // iteration this will be re-calculated based on the actual bed position
+ auto magnitude2 = [&](const uint8_t i, const uint8_t j) {
+ const xy_pos_t diff = z_stepper_align.xy[i] - z_stepper_align.xy[j];
+ return HYPOT2(diff.x, diff.y);
+ };
+ float z_probe = Z_BASIC_CLEARANCE + (G34_MAX_GRADE) * 0.01f * SQRT(
+ #if NUM_Z_STEPPER_DRIVERS == 3
+ _MAX(magnitude2(0, 1), magnitude2(1, 2), magnitude2(2, 0))
+ #elif NUM_Z_STEPPER_DRIVERS == 4
+ _MAX(magnitude2(0, 1), magnitude2(1, 2), magnitude2(2, 3),
+ magnitude2(3, 0), magnitude2(0, 2), magnitude2(1, 3))
+ #else
+ magnitude2(0, 1)
+ #endif
+ );
+
+ // Home before the alignment procedure
+ if (!all_axes_trusted()) home_all_axes();
+
+ // Move the Z coordinate realm towards the positive - dirty trick
+ current_position.z += z_probe * 0.5f;
+ sync_plan_position();
+ // Now, the Z origin lies below the build plate. That allows to probe deeper, before run_z_probe throws an error.
+ // This hack is un-done at the end of G34 - either by re-homing, or by using the probed heights of the last iteration.
+
+ #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ float last_z_align_move[NUM_Z_STEPPER_DRIVERS] = ARRAY_N(NUM_Z_STEPPER_DRIVERS, 10000.0f, 10000.0f, 10000.0f, 10000.0f);
+ #else
+ float last_z_align_level_indicator = 10000.0f;
+ #endif
+ float z_measured[NUM_Z_STEPPER_DRIVERS] = { 0 },
+ z_maxdiff = 0.0f,
+ amplification = z_auto_align_amplification;
+
+ #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ bool adjustment_reverse = false;
+ #endif
+
+ #if HAS_DISPLAY
+ PGM_P const msg_iteration = GET_TEXT(MSG_ITERATION);
+ const uint8_t iter_str_len = strlen_P(msg_iteration);
+ #endif
+
+ // Final z and iteration values will be used after breaking the loop
+ float z_measured_min;
+ uint8_t iteration = 0;
+ bool err_break = false; // To break out of nested loops
+ while (iteration < z_auto_align_iterations) {
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> probing all positions.");
+
+ const int iter = iteration + 1;
+ SERIAL_ECHOLNPAIR("\nG34 Iteration: ", iter);
+ #if HAS_DISPLAY
+ char str[iter_str_len + 2 + 1];
+ sprintf_P(str, msg_iteration, iter);
+ ui.set_status(str);
+ #endif
+
+ // Initialize minimum value
+ z_measured_min = 100000.0f;
+ float z_measured_max = -100000.0f;
+
+ // Probe all positions (one per Z-Stepper)
+ LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) {
+ // iteration odd/even --> downward / upward stepper sequence
+ const uint8_t iprobe = (iteration & 1) ? NUM_Z_STEPPER_DRIVERS - 1 - i : i;
+
+ // Safe clearance even on an incline
+ if ((iteration == 0 || i > 0) && z_probe > current_position.z) do_blocking_move_to_z(z_probe);
+
+ if (DEBUGGING(LEVELING))
+ DEBUG_ECHOLNPAIR_P(PSTR("Probing X"), z_stepper_align.xy[iprobe].x, SP_Y_STR, z_stepper_align.xy[iprobe].y);
+
+ // Probe a Z height for each stepper.
+ // Probing sanity check is disabled, as it would trigger even in normal cases because
+ // current_position.z has been manually altered in the "dirty trick" above.
+ const float z_probed_height = probe.probe_at_point(z_stepper_align.xy[iprobe], raise_after, 0, true, false);
+ if (isnan(z_probed_height)) {
+ SERIAL_ECHOLNPGM("Probing failed");
+ LCD_MESSAGEPGM(MSG_LCD_PROBING_FAILED);
+ err_break = true;
+ break;
+ }
+
+ // Add height to each value, to provide a more useful target height for
+ // the next iteration of probing. This allows adjustments to be made away from the bed.
+ z_measured[iprobe] = z_probed_height + Z_CLEARANCE_BETWEEN_PROBES;
+
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(iprobe + 1), " measured position is ", z_measured[iprobe]);
+
+ // Remember the minimum measurement to calculate the correction later on
+ z_measured_min = _MIN(z_measured_min, z_measured[iprobe]);
+ z_measured_max = _MAX(z_measured_max, z_measured[iprobe]);
+ } // for (i)
+
+ if (err_break) break;
+
+ // Adapt the next probe clearance height based on the new measurements.
+ // Safe_height = lowest distance to bed (= highest measurement) plus highest measured misalignment.
+ z_maxdiff = z_measured_max - z_measured_min;
+ z_probe = Z_BASIC_CLEARANCE + z_measured_max + z_maxdiff;
+
+ #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ // Replace the initial values in z_measured with calculated heights at
+ // each stepper position. This allows the adjustment algorithm to be
+ // shared between both possible probing mechanisms.
+
+ // This must be done after the next z_probe height is calculated, so that
+ // the height is calculated from actual print area positions, and not
+ // extrapolated motor movements.
+
+ // Compute the least-squares fit for all probed points.
+ // Calculate the Z position of each stepper and store it in z_measured.
+ // This allows the actual adjustment logic to be shared by both algorithms.
+ linear_fit_data lfd;
+ incremental_LSF_reset(&lfd);
+ LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) {
+ SERIAL_ECHOLNPAIR("PROBEPT_", int(i), ": ", z_measured[i]);
+ incremental_LSF(&lfd, z_stepper_align.xy[i], z_measured[i]);
+ }
+ finish_incremental_LSF(&lfd);
+
+ z_measured_min = 100000.0f;
+ LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS) {
+ z_measured[i] = -(lfd.A * z_stepper_align.stepper_xy[i].x + lfd.B * z_stepper_align.stepper_xy[i].y + lfd.D);
+ z_measured_min = _MIN(z_measured_min, z_measured[i]);
+ }
+
+ SERIAL_ECHOLNPAIR("CALCULATED STEPPER POSITIONS: Z1=", z_measured[0], " Z2=", z_measured[1], " Z3=", z_measured[2]);
+ #endif
+
+ SERIAL_ECHOLNPAIR("\n"
+ "DIFFERENCE Z1-Z2=", ABS(z_measured[0] - z_measured[1])
+ #if NUM_Z_STEPPER_DRIVERS == 3
+ , " Z2-Z3=", ABS(z_measured[1] - z_measured[2])
+ , " Z3-Z1=", ABS(z_measured[2] - z_measured[0])
+ #endif
+ );
+ #if HAS_DISPLAY
+ char fstr1[10];
+ #if NUM_Z_STEPPER_DRIVERS == 2
+ char msg[6 + (6 + 5) * 1 + 1];
+ #else
+ char msg[6 + (6 + 5) * 3 + 1], fstr2[10], fstr3[10];
+ #endif
+ sprintf_P(msg,
+ PSTR("Diffs Z1-Z2=%s"
+ #if NUM_Z_STEPPER_DRIVERS == 3
+ " Z2-Z3=%s"
+ " Z3-Z1=%s"
+ #endif
+ ), dtostrf(ABS(z_measured[0] - z_measured[1]), 1, 3, fstr1)
+ #if NUM_Z_STEPPER_DRIVERS == 3
+ , dtostrf(ABS(z_measured[1] - z_measured[2]), 1, 3, fstr2)
+ , dtostrf(ABS(z_measured[2] - z_measured[0]), 1, 3, fstr3)
+ #endif
+ );
+ ui.set_status(msg);
+ #endif
+
+ auto decreasing_accuracy = [](const float &v1, const float &v2){
+ if (v1 < v2 * 0.7f) {
+ SERIAL_ECHOLNPGM("Decreasing Accuracy Detected.");
+ LCD_MESSAGEPGM(MSG_DECREASING_ACCURACY);
+ return true;
+ }
+ return false;
+ };
+
+ #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ // Check if the applied corrections go in the correct direction.
+ // Calculate the sum of the absolute deviations from the mean of the probe measurements.
+ // Compare to the last iteration to ensure it's getting better.
+
+ // Calculate mean value as a reference
+ float z_measured_mean = 0.0f;
+ LOOP_L_N(zstepper, NUM_Z_STEPPER_DRIVERS) z_measured_mean += z_measured[zstepper];
+ z_measured_mean /= NUM_Z_STEPPER_DRIVERS;
+
+ // Calculate the sum of the absolute deviations from the mean value
+ float z_align_level_indicator = 0.0f;
+ LOOP_L_N(zstepper, NUM_Z_STEPPER_DRIVERS)
+ z_align_level_indicator += ABS(z_measured[zstepper] - z_measured_mean);
+
+ // If it's getting worse, stop and throw an error
+ err_break = decreasing_accuracy(last_z_align_level_indicator, z_align_level_indicator);
+ if (err_break) break;
+
+ last_z_align_level_indicator = z_align_level_indicator;
+ #endif
+
+ // The following correction actions are to be enabled for select Z-steppers only
+ stepper.set_separate_multi_axis(true);
+
+ bool success_break = true;
+ // Correct the individual stepper offsets
+ LOOP_L_N(zstepper, NUM_Z_STEPPER_DRIVERS) {
+ // Calculate current stepper move
+ float z_align_move = z_measured[zstepper] - z_measured_min;
+ const float z_align_abs = ABS(z_align_move);
+
+ #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ // Optimize one iteration's correction based on the first measurements
+ if (z_align_abs) amplification = (iteration == 1) ? _MIN(last_z_align_move[zstepper] / z_align_abs, 2.0f) : z_auto_align_amplification;
+
+ // Check for less accuracy compared to last move
+ if (decreasing_accuracy(last_z_align_move[zstepper], z_align_abs)) {
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(zstepper + 1), " last_z_align_move = ", last_z_align_move[zstepper]);
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(zstepper + 1), " z_align_abs = ", z_align_abs);
+ adjustment_reverse = !adjustment_reverse;
+ }
+
+ // Remember the alignment for the next iteration, but only if steppers move,
+ // otherwise it would be just zero (in case this stepper was at z_measured_min already)
+ if (z_align_abs > 0) last_z_align_move[zstepper] = z_align_abs;
+ #endif
+
+ // Stop early if all measured points achieve accuracy target
+ if (z_align_abs > z_auto_align_accuracy) success_break = false;
+
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(zstepper + 1), " corrected by ", z_align_move);
+
+ // Lock all steppers except one
+ stepper.set_all_z_lock(true, zstepper);
+
+ #if DISABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ // Decreasing accuracy was detected so move was inverted.
+ // Will match reversed Z steppers on dual steppers. Triple will need more work to map.
+ if (adjustment_reverse) {
+ z_align_move = -z_align_move;
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("> Z", int(zstepper + 1), " correction reversed to ", z_align_move);
+ }
+ #endif
+
+ // Do a move to correct part of the misalignment for the current stepper
+ do_blocking_move_to_z(amplification * z_align_move + current_position.z);
+ } // for (zstepper)
+
+ // Back to normal stepper operations
+ stepper.set_all_z_lock(false);
+ stepper.set_separate_multi_axis(false);
+
+ if (err_break) break;
+
+ if (success_break) {
+ SERIAL_ECHOLNPGM("Target accuracy achieved.");
+ LCD_MESSAGEPGM(MSG_ACCURACY_ACHIEVED);
+ break;
+ }
+
+ iteration++;
+ } // while (iteration < z_auto_align_iterations)
+
+ if (err_break)
+ SERIAL_ECHOLNPGM("G34 aborted.");
+ else {
+ SERIAL_ECHOLNPAIR("Did ", int(iteration + (iteration != z_auto_align_iterations)), " of ", int(z_auto_align_iterations));
+ SERIAL_ECHOLNPAIR_F("Accuracy: ", z_maxdiff);
+ }
+
+ // Stow the probe, as the last call to probe.probe_at_point(...) left
+ // the probe deployed if it was successful.
+ probe.stow();
+
+ #if ENABLED(HOME_AFTER_G34)
+ // After this operation the z position needs correction
+ set_axis_never_homed(Z_AXIS);
+ // Home Z after the alignment procedure
+ process_subcommands_now_P(PSTR("G28Z"));
+ #else
+ // Use the probed height from the last iteration to determine the Z height.
+ // z_measured_min is used, because all steppers are aligned to z_measured_min.
+ // Ideally, this would be equal to the 'z_probe * 0.5f' which was added earlier.
+ current_position.z -= z_measured_min - (float)Z_CLEARANCE_BETWEEN_PROBES;
+ sync_plan_position();
+ #endif
+
+ // Restore the active tool after homing
+ TERN_(HAS_MULTI_HOTEND, tool_change(old_tool_index, DISABLED(PARKING_EXTRUDER))); // Fetch previous tool for parking extruder
+
+ #if BOTH(HAS_LEVELING, RESTORE_LEVELING_AFTER_G34)
+ set_bed_leveling_enabled(leveling_was_active);
+ #endif
+
+ }while(0);
+ #endif
+}
+
+#endif // Z_MULTI_ENDSTOPS || Z_STEPPER_AUTO_ALIGN
+
+#if ENABLED(Z_STEPPER_AUTO_ALIGN)
+
+/**
+ * M422: Set a Z-Stepper automatic alignment XY point.
+ * Use repeatedly to set multiple points.
+ *
+ * S<index> : Index of the probe point to set
+ *
+ * With Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS:
+ * W<index> : Index of the Z stepper position to set
+ * The W and S parameters may not be combined.
+ *
+ * S and W require an X and/or Y parameter
+ * X<pos> : X position to set (Unchanged if omitted)
+ * Y<pos> : Y position to set (Unchanged if omitted)
+ *
+ * R : Recalculate points based on current probe offsets
+ */
+void GcodeSuite::M422() {
+
+ if (parser.seen('R')) {
+ z_stepper_align.reset_to_default();
+ return;
+ }
+
+ if (!parser.seen_any()) {
+ LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS)
+ SERIAL_ECHOLNPAIR_P(PSTR("M422 S"), int(i + 1), SP_X_STR, z_stepper_align.xy[i].x, SP_Y_STR, z_stepper_align.xy[i].y);
+ #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ LOOP_L_N(i, NUM_Z_STEPPER_DRIVERS)
+ SERIAL_ECHOLNPAIR_P(PSTR("M422 W"), int(i + 1), SP_X_STR, z_stepper_align.stepper_xy[i].x, SP_Y_STR, z_stepper_align.stepper_xy[i].y);
+ #endif
+ return;
+ }
+
+ const bool is_probe_point = parser.seen('S');
+
+ if (TERN0(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS, is_probe_point && parser.seen('W'))) {
+ SERIAL_ECHOLNPGM("?(S) and (W) may not be combined.");
+ return;
+ }
+
+ xy_pos_t *pos_dest = (
+ TERN_(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS, !is_probe_point ? z_stepper_align.stepper_xy :)
+ z_stepper_align.xy
+ );
+
+ if (!is_probe_point && TERN1(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS, !parser.seen('W'))) {
+ SERIAL_ECHOLNPGM("?(S)" TERN_(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS, " or (W)") " is required.");
+ return;
+ }
+
+ // Get the Probe Position Index or Z Stepper Index
+ int8_t position_index;
+ if (is_probe_point) {
+ position_index = parser.intval('S') - 1;
+ if (!WITHIN(position_index, 0, int8_t(NUM_Z_STEPPER_DRIVERS) - 1)) {
+ SERIAL_ECHOLNPGM("?(S) Probe-position index invalid.");
+ return;
+ }
+ }
+ else {
+ #if ENABLED(Z_STEPPER_ALIGN_KNOWN_STEPPER_POSITIONS)
+ position_index = parser.intval('W') - 1;
+ if (!WITHIN(position_index, 0, NUM_Z_STEPPER_DRIVERS - 1)) {
+ SERIAL_ECHOLNPGM("?(W) Z-stepper index invalid.");
+ return;
+ }
+ #endif
+ }
+
+ const xy_pos_t pos = {
+ parser.floatval('X', pos_dest[position_index].x),
+ parser.floatval('Y', pos_dest[position_index].y)
+ };
+
+ if (is_probe_point) {
+ if (!probe.can_reach(pos.x, Y_CENTER)) {
+ SERIAL_ECHOLNPGM("?(X) out of bounds.");
+ return;
+ }
+ if (!probe.can_reach(pos)) {
+ SERIAL_ECHOLNPGM("?(Y) out of bounds.");
+ return;
+ }
+ }
+
+ pos_dest[position_index] = pos;
+}
+
+#endif // Z_STEPPER_AUTO_ALIGN
diff --git a/Marlin/src/gcode/calibrate/G425.cpp b/Marlin/src/gcode/calibrate/G425.cpp
new file mode 100644
index 0000000..9510da7
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/G425.cpp
@@ -0,0 +1,623 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../MarlinCore.h"
+
+#if ENABLED(CALIBRATION_GCODE)
+
+#include "../gcode.h"
+
+#if ENABLED(BACKLASH_GCODE)
+ #include "../../feature/backlash.h"
+#endif
+
+#include "../../lcd/marlinui.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+#include "../../module/tool_change.h"
+#include "../../module/endstops.h"
+#include "../../feature/bedlevel/bedlevel.h"
+
+#if !AXIS_CAN_CALIBRATE(X)
+ #undef CALIBRATION_MEASURE_LEFT
+ #undef CALIBRATION_MEASURE_RIGHT
+#endif
+
+#if !AXIS_CAN_CALIBRATE(Y)
+ #undef CALIBRATION_MEASURE_FRONT
+ #undef CALIBRATION_MEASURE_BACK
+#endif
+
+#if !AXIS_CAN_CALIBRATE(Z)
+ #undef CALIBRATION_MEASURE_AT_TOP_EDGES
+#endif
+
+/**
+ * G425 backs away from the calibration object by various distances
+ * depending on the confidence level:
+ *
+ * UNKNOWN - No real notion on where the calibration object is on the bed
+ * UNCERTAIN - Measurement may be uncertain due to backlash
+ * CERTAIN - Measurement obtained with backlash compensation
+ */
+
+#ifndef CALIBRATION_MEASUREMENT_UNKNOWN
+ #define CALIBRATION_MEASUREMENT_UNKNOWN 5.0 // mm
+#endif
+#ifndef CALIBRATION_MEASUREMENT_UNCERTAIN
+ #define CALIBRATION_MEASUREMENT_UNCERTAIN 1.0 // mm
+#endif
+#ifndef CALIBRATION_MEASUREMENT_CERTAIN
+ #define CALIBRATION_MEASUREMENT_CERTAIN 0.5 // mm
+#endif
+
+#if BOTH(CALIBRATION_MEASURE_LEFT, CALIBRATION_MEASURE_RIGHT)
+ #define HAS_X_CENTER 1
+#endif
+#if BOTH(CALIBRATION_MEASURE_FRONT, CALIBRATION_MEASURE_BACK)
+ #define HAS_Y_CENTER 1
+#endif
+
+enum side_t : uint8_t { TOP, RIGHT, FRONT, LEFT, BACK, NUM_SIDES };
+
+static constexpr xyz_pos_t true_center CALIBRATION_OBJECT_CENTER;
+static constexpr xyz_float_t dimensions CALIBRATION_OBJECT_DIMENSIONS;
+static constexpr xy_float_t nod = { CALIBRATION_NOZZLE_OUTER_DIAMETER, CALIBRATION_NOZZLE_OUTER_DIAMETER };
+
+struct measurements_t {
+ xyz_pos_t obj_center = true_center; // Non-static must be assigned from xyz_pos_t
+
+ float obj_side[NUM_SIDES], backlash[NUM_SIDES];
+ xyz_float_t pos_error;
+
+ xy_float_t nozzle_outer_dimension = nod;
+};
+
+#if ENABLED(BACKLASH_GCODE)
+ #define TEMPORARY_BACKLASH_CORRECTION(value) REMEMBER(tbst, backlash.correction, value)
+#else
+ #define TEMPORARY_BACKLASH_CORRECTION(value)
+#endif
+
+#if ENABLED(BACKLASH_GCODE) && defined(BACKLASH_SMOOTHING_MM)
+ #define TEMPORARY_BACKLASH_SMOOTHING(value) REMEMBER(tbsm, backlash.smoothing_mm, value)
+#else
+ #define TEMPORARY_BACKLASH_SMOOTHING(value)
+#endif
+
+inline void calibration_move() {
+ do_blocking_move_to(current_position, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL));
+}
+
+/**
+ * Move to the exact center above the calibration object
+ *
+ * m in - Measurement record
+ * uncertainty in - How far away from the object top to park
+ */
+inline void park_above_object(measurements_t &m, const float uncertainty) {
+ // Move to safe distance above calibration object
+ current_position.z = m.obj_center.z + dimensions.z / 2 + uncertainty;
+ calibration_move();
+
+ // Move to center of calibration object in XY
+ current_position = xy_pos_t(m.obj_center);
+ calibration_move();
+}
+
+#if HAS_MULTI_HOTEND
+ inline void set_nozzle(measurements_t &m, const uint8_t extruder) {
+ if (extruder != active_extruder) {
+ park_above_object(m, CALIBRATION_MEASUREMENT_UNKNOWN);
+ tool_change(extruder);
+ }
+ }
+#endif
+
+#if HAS_HOTEND_OFFSET
+
+ inline void normalize_hotend_offsets() {
+ LOOP_S_L_N(e, 1, HOTENDS)
+ hotend_offset[e] -= hotend_offset[0];
+ hotend_offset[0].reset();
+ }
+
+#endif
+
+#if !PIN_EXISTS(CALIBRATION)
+ #include "../../module/probe.h"
+#endif
+
+inline bool read_calibration_pin() {
+ return (
+ #if PIN_EXISTS(CALIBRATION)
+ READ(CALIBRATION_PIN) != CALIBRATION_PIN_INVERTING
+ #else
+ PROBE_TRIGGERED()
+ #endif
+ );
+}
+
+/**
+ * Move along axis in the specified dir until the probe value becomes stop_state,
+ * then return the axis value.
+ *
+ * axis in - Axis along which the measurement will take place
+ * dir in - Direction along that axis (-1 or 1)
+ * stop_state in - Move until probe pin becomes this value
+ * fast in - Fast vs. precise measurement
+ */
+float measuring_movement(const AxisEnum axis, const int dir, const bool stop_state, const bool fast) {
+ const float step = fast ? 0.25 : CALIBRATION_MEASUREMENT_RESOLUTION;
+ const feedRate_t mms = fast ? MMM_TO_MMS(CALIBRATION_FEEDRATE_FAST) : MMM_TO_MMS(CALIBRATION_FEEDRATE_SLOW);
+ const float limit = fast ? 50 : 5;
+
+ destination = current_position;
+ for (float travel = 0; travel < limit; travel += step) {
+ destination[axis] += dir * step;
+ do_blocking_move_to(destination, mms);
+ planner.synchronize();
+ if (read_calibration_pin() == stop_state) break;
+ }
+ return destination[axis];
+}
+
+/**
+ * Move along axis until the probe is triggered. Move toolhead to its starting
+ * point and return the measured value.
+ *
+ * axis in - Axis along which the measurement will take place
+ * dir in - Direction along that axis (-1 or 1)
+ * stop_state in - Move until probe pin becomes this value
+ * backlash_ptr in/out - When not nullptr, measure and record axis backlash
+ * uncertainty in - If uncertainty is CALIBRATION_MEASUREMENT_UNKNOWN, do a fast probe.
+ */
+inline float measure(const AxisEnum axis, const int dir, const bool stop_state, float * const backlash_ptr, const float uncertainty) {
+ const bool fast = uncertainty == CALIBRATION_MEASUREMENT_UNKNOWN;
+
+ // Save position
+ destination = current_position;
+ const float start_pos = destination[axis];
+ const float measured_pos = measuring_movement(axis, dir, stop_state, fast);
+ // Measure backlash
+ if (backlash_ptr && !fast) {
+ const float release_pos = measuring_movement(axis, -dir, !stop_state, fast);
+ *backlash_ptr = ABS(release_pos - measured_pos);
+ }
+ // Return to starting position
+ destination[axis] = start_pos;
+ do_blocking_move_to(destination, MMM_TO_MMS(CALIBRATION_FEEDRATE_TRAVEL));
+ return measured_pos;
+}
+
+/**
+ * Probe one side of the calibration object
+ *
+ * m in/out - Measurement record, m.obj_center and m.obj_side will be updated.
+ * uncertainty in - How far away from the calibration object to begin probing
+ * side in - Side of probe where probe will occur
+ * probe_top_at_edge in - When probing sides, probe top of calibration object nearest edge
+ * to find out height of edge
+ */
+inline void probe_side(measurements_t &m, const float uncertainty, const side_t side, const bool probe_top_at_edge=false) {
+ const xyz_float_t dimensions = CALIBRATION_OBJECT_DIMENSIONS;
+ AxisEnum axis;
+ float dir = 1;
+
+ park_above_object(m, uncertainty);
+
+ switch (side) {
+ #if AXIS_CAN_CALIBRATE(Z)
+ case TOP: {
+ const float measurement = measure(Z_AXIS, -1, true, &m.backlash[TOP], uncertainty);
+ m.obj_center.z = measurement - dimensions.z / 2;
+ m.obj_side[TOP] = measurement;
+ return;
+ }
+ #endif
+ #if AXIS_CAN_CALIBRATE(X)
+ case LEFT: axis = X_AXIS; break;
+ case RIGHT: axis = X_AXIS; dir = -1; break;
+ #endif
+ #if AXIS_CAN_CALIBRATE(Y)
+ case FRONT: axis = Y_AXIS; break;
+ case BACK: axis = Y_AXIS; dir = -1; break;
+ #endif
+ default: return;
+ }
+
+ if (probe_top_at_edge) {
+ #if AXIS_CAN_CALIBRATE(Z)
+ // Probe top nearest the side we are probing
+ current_position[axis] = m.obj_center[axis] + (-dir) * (dimensions[axis] / 2 - m.nozzle_outer_dimension[axis]);
+ calibration_move();
+ m.obj_side[TOP] = measure(Z_AXIS, -1, true, &m.backlash[TOP], uncertainty);
+ m.obj_center.z = m.obj_side[TOP] - dimensions.z / 2;
+ #endif
+ }
+
+ if ((AXIS_CAN_CALIBRATE(X) && axis == X_AXIS) || (AXIS_CAN_CALIBRATE(Y) && axis == Y_AXIS)) {
+ // Move to safe distance to the side of the calibration object
+ current_position[axis] = m.obj_center[axis] + (-dir) * (dimensions[axis] / 2 + m.nozzle_outer_dimension[axis] / 2 + uncertainty);
+ calibration_move();
+
+ // Plunge below the side of the calibration object and measure
+ current_position.z = m.obj_side[TOP] - (CALIBRATION_NOZZLE_TIP_HEIGHT) * 0.7f;
+ calibration_move();
+ const float measurement = measure(axis, dir, true, &m.backlash[side], uncertainty);
+ m.obj_center[axis] = measurement + dir * (dimensions[axis] / 2 + m.nozzle_outer_dimension[axis] / 2);
+ m.obj_side[side] = measurement;
+ }
+}
+
+/**
+ * Probe all sides of the calibration calibration object
+ *
+ * m in/out - Measurement record: center, backlash and error values be updated.
+ * uncertainty in - How far away from the calibration object to begin probing
+ */
+inline void probe_sides(measurements_t &m, const float uncertainty) {
+ #if ENABLED(CALIBRATION_MEASURE_AT_TOP_EDGES)
+ constexpr bool probe_top_at_edge = true;
+ #else
+ // Probing at the exact center only works if the center is flat. Probing on a washer
+ // or bolt will require probing the top near the side edges, away from the center.
+ constexpr bool probe_top_at_edge = false;
+ probe_side(m, uncertainty, TOP);
+ #endif
+
+ TERN_(CALIBRATION_MEASURE_RIGHT, probe_side(m, uncertainty, RIGHT, probe_top_at_edge));
+ TERN_(CALIBRATION_MEASURE_FRONT, probe_side(m, uncertainty, FRONT, probe_top_at_edge));
+ TERN_(CALIBRATION_MEASURE_LEFT, probe_side(m, uncertainty, LEFT, probe_top_at_edge));
+ TERN_(CALIBRATION_MEASURE_BACK, probe_side(m, uncertainty, BACK, probe_top_at_edge));
+
+ // Compute the measured center of the calibration object.
+ TERN_(HAS_X_CENTER, m.obj_center.x = (m.obj_side[LEFT] + m.obj_side[RIGHT]) / 2);
+ TERN_(HAS_Y_CENTER, m.obj_center.y = (m.obj_side[FRONT] + m.obj_side[BACK]) / 2);
+
+ // Compute the outside diameter of the nozzle at the height
+ // at which it makes contact with the calibration object
+ TERN_(HAS_X_CENTER, m.nozzle_outer_dimension.x = m.obj_side[RIGHT] - m.obj_side[LEFT] - dimensions.x);
+ TERN_(HAS_Y_CENTER, m.nozzle_outer_dimension.y = m.obj_side[BACK] - m.obj_side[FRONT] - dimensions.y);
+
+ park_above_object(m, uncertainty);
+
+ // The difference between the known and the measured location
+ // of the calibration object is the positional error
+ m.pos_error.x = (0
+ #if HAS_X_CENTER
+ + true_center.x - m.obj_center.x
+ #endif
+ );
+ m.pos_error.y = (0
+ #if HAS_Y_CENTER
+ + true_center.y - m.obj_center.y
+ #endif
+ );
+ m.pos_error.z = true_center.z - m.obj_center.z;
+}
+
+#if ENABLED(CALIBRATION_REPORTING)
+ inline void report_measured_faces(const measurements_t &m) {
+ SERIAL_ECHOLNPGM("Sides:");
+ #if AXIS_CAN_CALIBRATE(Z)
+ SERIAL_ECHOLNPAIR(" Top: ", m.obj_side[TOP]);
+ #endif
+ #if ENABLED(CALIBRATION_MEASURE_LEFT)
+ SERIAL_ECHOLNPAIR(" Left: ", m.obj_side[LEFT]);
+ #endif
+ #if ENABLED(CALIBRATION_MEASURE_RIGHT)
+ SERIAL_ECHOLNPAIR(" Right: ", m.obj_side[RIGHT]);
+ #endif
+ #if ENABLED(CALIBRATION_MEASURE_FRONT)
+ SERIAL_ECHOLNPAIR(" Front: ", m.obj_side[FRONT]);
+ #endif
+ #if ENABLED(CALIBRATION_MEASURE_BACK)
+ SERIAL_ECHOLNPAIR(" Back: ", m.obj_side[BACK]);
+ #endif
+ SERIAL_EOL();
+ }
+
+ inline void report_measured_center(const measurements_t &m) {
+ SERIAL_ECHOLNPGM("Center:");
+ #if HAS_X_CENTER
+ SERIAL_ECHOLNPAIR_P(SP_X_STR, m.obj_center.x);
+ #endif
+ #if HAS_Y_CENTER
+ SERIAL_ECHOLNPAIR_P(SP_Y_STR, m.obj_center.y);
+ #endif
+ SERIAL_ECHOLNPAIR_P(SP_Z_STR, m.obj_center.z);
+ SERIAL_EOL();
+ }
+
+ inline void report_measured_backlash(const measurements_t &m) {
+ SERIAL_ECHOLNPGM("Backlash:");
+ #if AXIS_CAN_CALIBRATE(X)
+ #if ENABLED(CALIBRATION_MEASURE_LEFT)
+ SERIAL_ECHOLNPAIR(" Left: ", m.backlash[LEFT]);
+ #endif
+ #if ENABLED(CALIBRATION_MEASURE_RIGHT)
+ SERIAL_ECHOLNPAIR(" Right: ", m.backlash[RIGHT]);
+ #endif
+ #endif
+ #if AXIS_CAN_CALIBRATE(Y)
+ #if ENABLED(CALIBRATION_MEASURE_FRONT)
+ SERIAL_ECHOLNPAIR(" Front: ", m.backlash[FRONT]);
+ #endif
+ #if ENABLED(CALIBRATION_MEASURE_BACK)
+ SERIAL_ECHOLNPAIR(" Back: ", m.backlash[BACK]);
+ #endif
+ #endif
+ #if AXIS_CAN_CALIBRATE(Z)
+ SERIAL_ECHOLNPAIR(" Top: ", m.backlash[TOP]);
+ #endif
+ SERIAL_EOL();
+ }
+
+ inline void report_measured_positional_error(const measurements_t &m) {
+ SERIAL_CHAR('T');
+ SERIAL_ECHO(int(active_extruder));
+ SERIAL_ECHOLNPGM(" Positional Error:");
+ #if HAS_X_CENTER
+ SERIAL_ECHOLNPAIR_P(SP_X_STR, m.pos_error.x);
+ #endif
+ #if HAS_Y_CENTER
+ SERIAL_ECHOLNPAIR_P(SP_Y_STR, m.pos_error.y);
+ #endif
+ if (AXIS_CAN_CALIBRATE(Z)) SERIAL_ECHOLNPAIR_P(SP_Z_STR, m.pos_error.z);
+ SERIAL_EOL();
+ }
+
+ inline void report_measured_nozzle_dimensions(const measurements_t &m) {
+ SERIAL_ECHOLNPGM("Nozzle Tip Outer Dimensions:");
+ #if HAS_X_CENTER || HAS_Y_CENTER
+ #if HAS_X_CENTER
+ SERIAL_ECHOLNPAIR_P(SP_X_STR, m.nozzle_outer_dimension.x);
+ #endif
+ #if HAS_Y_CENTER
+ SERIAL_ECHOLNPAIR_P(SP_Y_STR, m.nozzle_outer_dimension.y);
+ #endif
+ #else
+ UNUSED(m);
+ #endif
+ SERIAL_EOL();
+ }
+
+ #if HAS_HOTEND_OFFSET
+ //
+ // This function requires normalize_hotend_offsets() to be called
+ //
+ inline void report_hotend_offsets() {
+ LOOP_S_L_N(e, 1, HOTENDS)
+ SERIAL_ECHOLNPAIR_P(PSTR("T"), int(e), PSTR(" Hotend Offset X"), hotend_offset[e].x, SP_Y_STR, hotend_offset[e].y, SP_Z_STR, hotend_offset[e].z);
+ }
+ #endif
+
+#endif // CALIBRATION_REPORTING
+
+/**
+ * Probe around the calibration object to measure backlash
+ *
+ * m in/out - Measurement record, updated with new readings
+ * uncertainty in - How far away from the object to begin probing
+ */
+inline void calibrate_backlash(measurements_t &m, const float uncertainty) {
+ // Backlash compensation should be off while measuring backlash
+
+ {
+ // New scope for TEMPORARY_BACKLASH_CORRECTION
+ TEMPORARY_BACKLASH_CORRECTION(all_off);
+ TEMPORARY_BACKLASH_SMOOTHING(0.0f);
+
+ probe_sides(m, uncertainty);
+
+ #if ENABLED(BACKLASH_GCODE)
+
+ #if HAS_X_CENTER
+ backlash.distance_mm.x = (m.backlash[LEFT] + m.backlash[RIGHT]) / 2;
+ #elif ENABLED(CALIBRATION_MEASURE_LEFT)
+ backlash.distance_mm.x = m.backlash[LEFT];
+ #elif ENABLED(CALIBRATION_MEASURE_RIGHT)
+ backlash.distance_mm.x = m.backlash[RIGHT];
+ #endif
+
+ #if HAS_Y_CENTER
+ backlash.distance_mm.y = (m.backlash[FRONT] + m.backlash[BACK]) / 2;
+ #elif ENABLED(CALIBRATION_MEASURE_FRONT)
+ backlash.distance_mm.y = m.backlash[FRONT];
+ #elif ENABLED(CALIBRATION_MEASURE_BACK)
+ backlash.distance_mm.y = m.backlash[BACK];
+ #endif
+
+ if (AXIS_CAN_CALIBRATE(Z)) backlash.distance_mm.z = m.backlash[TOP];
+ #endif
+ }
+
+ #if ENABLED(BACKLASH_GCODE)
+ // Turn on backlash compensation and move in all
+ // allowed directions to take up any backlash
+ {
+ // New scope for TEMPORARY_BACKLASH_CORRECTION
+ TEMPORARY_BACKLASH_CORRECTION(all_on);
+ TEMPORARY_BACKLASH_SMOOTHING(0.0f);
+ const xyz_float_t move = { AXIS_CAN_CALIBRATE(X) * 3, AXIS_CAN_CALIBRATE(Y) * 3, AXIS_CAN_CALIBRATE(Z) * 3 };
+ current_position += move; calibration_move();
+ current_position -= move; calibration_move();
+ }
+ #endif
+}
+
+inline void update_measurements(measurements_t &m, const AxisEnum axis) {
+ current_position[axis] += m.pos_error[axis];
+ m.obj_center[axis] = true_center[axis];
+ m.pos_error[axis] = 0;
+}
+
+/**
+ * Probe around the calibration object. Adjust the position and toolhead offset
+ * using the deviation from the known position of the calibration object.
+ *
+ * m in/out - Measurement record, updated with new readings
+ * uncertainty in - How far away from the object to begin probing
+ * extruder in - What extruder to probe
+ *
+ * Prerequisites:
+ * - Call calibrate_backlash() beforehand for best accuracy
+ */
+inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const uint8_t extruder) {
+ TEMPORARY_BACKLASH_CORRECTION(all_on);
+ TEMPORARY_BACKLASH_SMOOTHING(0.0f);
+
+ #if HAS_MULTI_HOTEND
+ set_nozzle(m, extruder);
+ #else
+ UNUSED(extruder);
+ #endif
+
+ probe_sides(m, uncertainty);
+
+ // Adjust the hotend offset
+ #if HAS_HOTEND_OFFSET
+ if (ENABLED(HAS_X_CENTER) && AXIS_CAN_CALIBRATE(X)) hotend_offset[extruder].x += m.pos_error.x;
+ if (ENABLED(HAS_Y_CENTER) && AXIS_CAN_CALIBRATE(Y)) hotend_offset[extruder].y += m.pos_error.y;
+ if (AXIS_CAN_CALIBRATE(Z)) hotend_offset[extruder].z += m.pos_error.z;
+ normalize_hotend_offsets();
+ #endif
+
+ // Correct for positional error, so the object
+ // is at the known actual spot
+ planner.synchronize();
+ if (ENABLED(HAS_X_CENTER) && AXIS_CAN_CALIBRATE(X)) update_measurements(m, X_AXIS);
+ if (ENABLED(HAS_Y_CENTER) && AXIS_CAN_CALIBRATE(Y)) update_measurements(m, Y_AXIS);
+ if (AXIS_CAN_CALIBRATE(Z)) update_measurements(m, Z_AXIS);
+
+ sync_plan_position();
+}
+
+/**
+ * Probe around the calibration object for all toolheads, adjusting the coordinate
+ * system for the first nozzle and the nozzle offset for subsequent nozzles.
+ *
+ * m in/out - Measurement record, updated with new readings
+ * uncertainty in - How far away from the object to begin probing
+ */
+inline void calibrate_all_toolheads(measurements_t &m, const float uncertainty) {
+ TEMPORARY_BACKLASH_CORRECTION(all_on);
+ TEMPORARY_BACKLASH_SMOOTHING(0.0f);
+
+ HOTEND_LOOP() calibrate_toolhead(m, uncertainty, e);
+
+ TERN_(HAS_HOTEND_OFFSET, normalize_hotend_offsets());
+
+ TERN_(HAS_MULTI_HOTEND, set_nozzle(m, 0));
+}
+
+/**
+ * Perform a full auto-calibration routine:
+ *
+ * 1) For each nozzle, touch top and sides of object to determine object position and
+ * nozzle offsets. Do a fast but rough search over a wider area.
+ * 2) With the first nozzle, touch top and sides of object to determine backlash values
+ * for all axis (if BACKLASH_GCODE is enabled)
+ * 3) For each nozzle, touch top and sides of object slowly to determine precise
+ * position of object. Adjust coordinate system and nozzle offsets so probed object
+ * location corresponds to known object location with a high degree of precision.
+ */
+inline void calibrate_all() {
+ measurements_t m;
+
+ TERN_(HAS_HOTEND_OFFSET, reset_hotend_offsets());
+
+ TEMPORARY_BACKLASH_CORRECTION(all_on);
+ TEMPORARY_BACKLASH_SMOOTHING(0.0f);
+
+ // Do a fast and rough calibration of the toolheads
+ calibrate_all_toolheads(m, CALIBRATION_MEASUREMENT_UNKNOWN);
+
+ TERN_(BACKLASH_GCODE, calibrate_backlash(m, CALIBRATION_MEASUREMENT_UNCERTAIN));
+
+ // Cycle the toolheads so the servos settle into their "natural" positions
+ #if HAS_MULTI_HOTEND
+ HOTEND_LOOP() set_nozzle(m, e);
+ #endif
+
+ // Do a slow and precise calibration of the toolheads
+ calibrate_all_toolheads(m, CALIBRATION_MEASUREMENT_UNCERTAIN);
+
+ current_position.x = X_CENTER;
+ calibration_move(); // Park nozzle away from calibration object
+}
+
+/**
+ * G425: Perform calibration with calibration object.
+ *
+ * B - Perform calibration of backlash only.
+ * T<extruder> - Perform calibration of toolhead only.
+ * V - Probe object and print position, error, backlash and hotend offset.
+ * U - Uncertainty, how far to start probe away from the object (mm)
+ *
+ * no args - Perform entire calibration sequence (backlash + position on all toolheads)
+ */
+void GcodeSuite::G425() {
+
+ #ifdef CALIBRATION_SCRIPT_PRE
+ GcodeSuite::process_subcommands_now_P(PSTR(CALIBRATION_SCRIPT_PRE));
+ #endif
+
+ if (homing_needed_error()) return;
+
+ TEMPORARY_BED_LEVELING_STATE(false);
+ SET_SOFT_ENDSTOP_LOOSE(true);
+
+ measurements_t m;
+ float uncertainty = parser.seenval('U') ? parser.value_float() : CALIBRATION_MEASUREMENT_UNCERTAIN;
+
+ if (parser.seen('B'))
+ calibrate_backlash(m, uncertainty);
+ else if (parser.seen('T'))
+ calibrate_toolhead(m, uncertainty, parser.has_value() ? parser.value_int() : active_extruder);
+ #if ENABLED(CALIBRATION_REPORTING)
+ else if (parser.seen('V')) {
+ probe_sides(m, uncertainty);
+ SERIAL_EOL();
+ report_measured_faces(m);
+ report_measured_center(m);
+ report_measured_backlash(m);
+ report_measured_nozzle_dimensions(m);
+ report_measured_positional_error(m);
+ #if HAS_HOTEND_OFFSET
+ normalize_hotend_offsets();
+ report_hotend_offsets();
+ #endif
+ }
+ #endif
+ else
+ calibrate_all();
+
+ SET_SOFT_ENDSTOP_LOOSE(false);
+
+ #ifdef CALIBRATION_SCRIPT_POST
+ GcodeSuite::process_subcommands_now_P(PSTR(CALIBRATION_SCRIPT_POST));
+ #endif
+}
+
+#endif // CALIBRATION_GCODE
diff --git a/Marlin/src/gcode/calibrate/G76_M192_M871.cpp b/Marlin/src/gcode/calibrate/G76_M192_M871.cpp
new file mode 100644
index 0000000..5d0bb0d
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/G76_M192_M871.cpp
@@ -0,0 +1,369 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * G76_M871.cpp - Temperature calibration/compensation for z-probing
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(PROBE_TEMP_COMPENSATION)
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+#include "../../module/probe.h"
+#include "../../feature/bedlevel/bedlevel.h"
+#include "../../module/temperature.h"
+#include "../../module/probe.h"
+#include "../../feature/probe_temp_comp.h"
+
+#include "../../lcd/marlinui.h"
+#include "../../MarlinCore.h" // for wait_for_heatup, idle()
+
+#if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ #include "../../module/printcounter.h"
+#endif
+
+#if ENABLED(PRINTER_EVENTS_LEDS)
+ #include "../../feature/leds/leds.h"
+#endif
+
+/**
+ * G76: calibrate probe and/or bed temperature offsets
+ * Notes:
+ * - When calibrating probe, bed temperature is held constant.
+ * Compensation values are deltas to first probe measurement at probe temp. = 30°C.
+ * - When calibrating bed, probe temperature is held constant.
+ * Compensation values are deltas to first probe measurement at bed temp. = 60°C.
+ * - The hotend will not be heated at any time.
+ * - On my Průša MK3S clone I put a piece of paper between the probe and the hotend
+ * so the hotend fan would not cool my probe constantly. Alternativly you could just
+ * make sure the fan is not running while running the calibration process.
+ *
+ * Probe calibration:
+ * - Moves probe to cooldown point.
+ * - Heats up bed to 100°C.
+ * - Moves probe to probing point (1mm above heatbed).
+ * - Waits until probe reaches target temperature (30°C).
+ * - Does a z-probing (=base value) and increases target temperature by 5°C.
+ * - Waits until probe reaches increased target temperature.
+ * - Does a z-probing (delta to base value will be a compensation value) and increases target temperature by 5°C.
+ * - Repeats last two steps until max. temperature reached or timeout (i.e. probe does not heat up any further).
+ * - Compensation values of higher temperatures will be extrapolated (using linear regression first).
+ * While this is not exact by any means it is still better than simply using the last compensation value.
+ *
+ * Bed calibration:
+ * - Moves probe to cooldown point.
+ * - Heats up bed to 60°C.
+ * - Moves probe to probing point (1mm above heatbed).
+ * - Waits until probe reaches target temperature (30°C).
+ * - Does a z-probing (=base value) and increases bed temperature by 5°C.
+ * - Moves probe to cooldown point.
+ * - Waits until probe is below 30°C and bed has reached target temperature.
+ * - Moves probe to probing point and waits until it reaches target temperature (30°C).
+ * - Does a z-probing (delta to base value will be a compensation value) and increases bed temperature by 5°C.
+ * - Repeats last four points until max. bed temperature reached (110°C) or timeout.
+ * - Compensation values of higher temperatures will be extrapolated (using linear regression first).
+ * While this is not exact by any means it is still better than simply using the last compensation value.
+ *
+ * G76 [B | P]
+ * - no flag - Both calibration procedures will be run.
+ * - `B` - Run bed temperature calibration.
+ * - `P` - Run probe temperature calibration.
+ */
+
+static void say_waiting_for() { SERIAL_ECHOPGM("Waiting for "); }
+static void say_waiting_for_probe_heating() { say_waiting_for(); SERIAL_ECHOLNPGM("probe heating."); }
+static void say_successfully_calibrated() { SERIAL_ECHOPGM("Successfully calibrated"); }
+static void say_failed_to_calibrate() { SERIAL_ECHOPGM("!Failed to calibrate"); }
+
+void GcodeSuite::G76() {
+ // Check if heated bed is available and z-homing is done with probe
+ #if TEMP_SENSOR_BED == 0 || !(HOMING_Z_WITH_PROBE)
+ return;
+ #endif
+
+ auto report_temps = [](millis_t &ntr, millis_t timeout=0) {
+ idle_no_sleep();
+ const millis_t ms = millis();
+ if (ELAPSED(ms, ntr)) {
+ ntr = ms + 1000;
+ thermalManager.print_heater_states(active_extruder);
+ }
+ return (timeout && ELAPSED(ms, timeout));
+ };
+
+ auto wait_for_temps = [&](const float tb, const float tp, millis_t &ntr, const millis_t timeout=0) {
+ say_waiting_for(); SERIAL_ECHOLNPGM("bed and probe temperature.");
+ while (fabs(thermalManager.degBed() - tb) > 0.1f || thermalManager.degProbe() > tp)
+ if (report_temps(ntr, timeout)) return true;
+ return false;
+ };
+
+ auto g76_probe = [](const TempSensorID sid, uint16_t &targ, const xy_pos_t &nozpos) {
+ do_z_clearance(5.0); // Raise nozzle before probing
+ const float measured_z = probe.probe_at_point(nozpos, PROBE_PT_STOW, 0, false); // verbose=0, probe_relative=false
+ if (isnan(measured_z))
+ SERIAL_ECHOLNPGM("!Received NAN. Aborting.");
+ else {
+ SERIAL_ECHOLNPAIR_F("Measured: ", measured_z);
+ if (targ == cali_info_init[sid].start_temp)
+ temp_comp.prepare_new_calibration(measured_z);
+ else
+ temp_comp.push_back_new_measurement(sid, measured_z);
+ targ += cali_info_init[sid].temp_res;
+ }
+ return measured_z;
+ };
+
+ #if ENABLED(BLTOUCH)
+ // Make sure any BLTouch error condition is cleared
+ bltouch_command(BLTOUCH_RESET, BLTOUCH_RESET_DELAY);
+ set_bltouch_deployed(false);
+ #endif
+
+ bool do_bed_cal = parser.boolval('B'), do_probe_cal = parser.boolval('P');
+ if (!do_bed_cal && !do_probe_cal) do_bed_cal = do_probe_cal = true;
+
+ // Synchronize with planner
+ planner.synchronize();
+
+ const xyz_pos_t parkpos = temp_comp.park_point,
+ probe_pos_xyz = xyz_pos_t(temp_comp.measure_point) + xyz_pos_t({ 0.0f, 0.0f, PTC_PROBE_HEATING_OFFSET }),
+ noz_pos_xyz = probe_pos_xyz - probe.offset_xy; // Nozzle position based on probe position
+
+ if (do_bed_cal || do_probe_cal) {
+ // Ensure park position is reachable
+ bool reachable = position_is_reachable(parkpos) || WITHIN(parkpos.z, Z_MIN_POS - fslop, Z_MAX_POS + fslop);
+ if (!reachable)
+ SERIAL_ECHOLNPGM("!Park");
+ else {
+ // Ensure probe position is reachable
+ reachable = probe.can_reach(probe_pos_xyz);
+ if (!reachable) SERIAL_ECHOLNPGM("!Probe");
+ }
+
+ if (!reachable) {
+ SERIAL_ECHOLNPGM(" position unreachable - aborting.");
+ return;
+ }
+
+ process_subcommands_now_P(G28_STR);
+ }
+
+ remember_feedrate_scaling_off();
+
+
+ /******************************************
+ * Calibrate bed temperature offsets
+ ******************************************/
+
+ // Report temperatures every second and handle heating timeouts
+ millis_t next_temp_report = millis() + 1000;
+
+ auto report_targets = [&](const uint16_t tb, const uint16_t tp) {
+ SERIAL_ECHOLNPAIR("Target Bed:", tb, " Probe:", tp);
+ };
+
+ if (do_bed_cal) {
+
+ uint16_t target_bed = cali_info_init[TSI_BED].start_temp,
+ target_probe = temp_comp.bed_calib_probe_temp;
+
+ say_waiting_for(); SERIAL_ECHOLNPGM(" cooling.");
+ while (thermalManager.degBed() > target_bed || thermalManager.degProbe() > target_probe)
+ report_temps(next_temp_report);
+
+ // Disable leveling so it won't mess with us
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(false));
+
+ for (;;) {
+ thermalManager.setTargetBed(target_bed);
+
+ report_targets(target_bed, target_probe);
+
+ // Park nozzle
+ do_blocking_move_to(parkpos);
+
+ // Wait for heatbed to reach target temp and probe to cool below target temp
+ if (wait_for_temps(target_bed, target_probe, next_temp_report, millis() + MIN_TO_MS(15))) {
+ SERIAL_ECHOLNPGM("!Bed heating timeout.");
+ break;
+ }
+
+ // Move the nozzle to the probing point and wait for the probe to reach target temp
+ do_blocking_move_to(noz_pos_xyz);
+ say_waiting_for_probe_heating();
+ SERIAL_EOL();
+ while (thermalManager.degProbe() < target_probe)
+ report_temps(next_temp_report);
+
+ const float measured_z = g76_probe(TSI_BED, target_bed, noz_pos_xyz);
+ if (isnan(measured_z) || target_bed > BED_MAX_TARGET) break;
+ }
+
+ SERIAL_ECHOLNPAIR("Retrieved measurements: ", temp_comp.get_index());
+ if (temp_comp.finish_calibration(TSI_BED)) {
+ say_successfully_calibrated();
+ SERIAL_ECHOLNPGM(" bed.");
+ }
+ else {
+ say_failed_to_calibrate();
+ SERIAL_ECHOLNPGM(" bed. Values reset.");
+ }
+
+ // Cleanup
+ thermalManager.setTargetBed(0);
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(true));
+ } // do_bed_cal
+
+ /********************************************
+ * Calibrate probe temperature offsets
+ ********************************************/
+
+ if (do_probe_cal) {
+
+ // Park nozzle
+ do_blocking_move_to(parkpos);
+
+ // Initialize temperatures
+ const uint16_t target_bed = temp_comp.probe_calib_bed_temp;
+ thermalManager.setTargetBed(target_bed);
+
+ uint16_t target_probe = cali_info_init[TSI_PROBE].start_temp;
+
+ report_targets(target_bed, target_probe);
+
+ // Wait for heatbed to reach target temp and probe to cool below target temp
+ wait_for_temps(target_bed, target_probe, next_temp_report);
+
+ // Disable leveling so it won't mess with us
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(false));
+
+ bool timeout = false;
+ for (;;) {
+ // Move probe to probing point and wait for it to reach target temperature
+ do_blocking_move_to(noz_pos_xyz);
+
+ say_waiting_for_probe_heating();
+ SERIAL_ECHOLNPAIR(" Bed:", target_bed, " Probe:", target_probe);
+ const millis_t probe_timeout_ms = millis() + SEC_TO_MS(900UL);
+ while (thermalManager.degProbe() < target_probe) {
+ if (report_temps(next_temp_report, probe_timeout_ms)) {
+ SERIAL_ECHOLNPGM("!Probe heating timed out.");
+ timeout = true;
+ break;
+ }
+ }
+ if (timeout) break;
+
+ const float measured_z = g76_probe(TSI_PROBE, target_probe, noz_pos_xyz);
+ if (isnan(measured_z) || target_probe > cali_info_init[TSI_PROBE].end_temp) break;
+ }
+
+ SERIAL_ECHOLNPAIR("Retrieved measurements: ", temp_comp.get_index());
+ if (temp_comp.finish_calibration(TSI_PROBE))
+ say_successfully_calibrated();
+ else
+ say_failed_to_calibrate();
+ SERIAL_ECHOLNPGM(" probe.");
+
+ // Cleanup
+ thermalManager.setTargetBed(0);
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(true));
+
+ SERIAL_ECHOLNPGM("Final compensation values:");
+ temp_comp.print_offsets();
+ } // do_probe_cal
+
+ restore_feedrate_and_scaling();
+}
+
+/**
+ * M871: Report / reset temperature compensation offsets.
+ * Note: This does not affect values in EEPROM until M500.
+ *
+ * M871 [ R | B | P | E ]
+ *
+ * No Parameters - Print current offset values.
+ *
+ * Select only one of these flags:
+ * R - Reset all offsets to zero (i.e., disable compensation).
+ * B - Manually set offset for bed
+ * P - Manually set offset for probe
+ * E - Manually set offset for extruder
+ *
+ * With B, P, or E:
+ * I[index] - Index in the array
+ * V[value] - Adjustment in µm
+ */
+void GcodeSuite::M871() {
+
+ if (parser.seen('R')) {
+ // Reset z-probe offsets to factory defaults
+ temp_comp.clear_all_offsets();
+ SERIAL_ECHOLNPGM("Offsets reset to default.");
+ }
+ else if (parser.seen("BPE")) {
+ if (!parser.seenval('V')) return;
+ const int16_t offset_val = parser.value_int();
+ if (!parser.seenval('I')) return;
+ const int16_t idx = parser.value_int();
+ const TempSensorID mod = (parser.seen('B') ? TSI_BED :
+ #if ENABLED(USE_TEMP_EXT_COMPENSATION)
+ parser.seen('E') ? TSI_EXT :
+ #endif
+ TSI_PROBE
+ );
+ if (idx > 0 && temp_comp.set_offset(mod, idx - 1, offset_val))
+ SERIAL_ECHOLNPAIR("Set value: ", offset_val);
+ else
+ SERIAL_ECHOLNPGM("!Invalid index. Failed to set value (note: value at index 0 is constant).");
+
+ }
+ else // Print current Z-probe adjustments. Note: Values in EEPROM might differ.
+ temp_comp.print_offsets();
+}
+
+/**
+ * M192: Wait for probe temperature sensor to reach a target
+ *
+ * Select only one of these flags:
+ * R - Wait for heating or cooling
+ * S - Wait only for heating
+ */
+void GcodeSuite::M192() {
+ if (DEBUGGING(DRYRUN)) return;
+
+ const bool no_wait_for_cooling = parser.seenval('S');
+ if (!no_wait_for_cooling && ! parser.seenval('R')) {
+ SERIAL_ERROR_MSG("No target temperature set.");
+ return;
+ }
+
+ const float target_temp = parser.value_celsius();
+ ui.set_status_P(thermalManager.isProbeBelowTemp(target_temp) ? GET_TEXT(MSG_PROBE_HEATING) : GET_TEXT(MSG_PROBE_COOLING));
+ thermalManager.wait_for_probe(target_temp, no_wait_for_cooling);
+}
+
+#endif // PROBE_TEMP_COMPENSATION
diff --git a/Marlin/src/gcode/calibrate/M100.cpp b/Marlin/src/gcode/calibrate/M100.cpp
new file mode 100644
index 0000000..9ac2380
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/M100.cpp
@@ -0,0 +1,379 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(M100_FREE_MEMORY_WATCHER)
+
+#include "../gcode.h"
+#include "../queue.h"
+#include "../../libs/hex_print.h"
+
+#include "../../MarlinCore.h" // for idle()
+
+/**
+ * M100 Free Memory Watcher
+ *
+ * This code watches the free memory block between the bottom of the heap and the top of the stack.
+ * This memory block is initialized and watched via the M100 command.
+ *
+ * M100 I Initializes the free memory block and prints vitals statistics about the area
+ *
+ * M100 F Identifies how much of the free memory block remains free and unused. It also
+ * detects and reports any corruption within the free memory block that may have
+ * happened due to errant firmware.
+ *
+ * M100 D Does a hex display of the free memory block along with a flag for any errant
+ * data that does not match the expected value.
+ *
+ * M100 C x Corrupts x locations within the free memory block. This is useful to check the
+ * correctness of the M100 F and M100 D commands.
+ *
+ * Also, there are two support functions that can be called from a developer's C code.
+ *
+ * uint16_t check_for_free_memory_corruption(PGM_P const free_memory_start);
+ * void M100_dump_routine(PGM_P const title, const char * const start, const char * const end);
+ *
+ * Initial version by Roxy-3D
+ */
+#define M100_FREE_MEMORY_DUMPER // Enable for the `M100 D` Dump sub-command
+#define M100_FREE_MEMORY_CORRUPTOR // Enable for the `M100 C` Corrupt sub-command
+
+#define TEST_BYTE ((char) 0xE5)
+
+#if EITHER(__AVR__, IS_32BIT_TEENSY)
+
+ extern char __bss_end;
+ char *end_bss = &__bss_end,
+ *free_memory_start = end_bss, *free_memory_end = 0,
+ *stacklimit = 0, *heaplimit = 0;
+
+ #define MEMORY_END_CORRECTION 0
+
+#elif defined(TARGET_LPC1768)
+
+ extern char __bss_end__, __StackLimit, __HeapLimit;
+
+ char *end_bss = &__bss_end__,
+ *stacklimit = &__StackLimit,
+ *heaplimit = &__HeapLimit;
+
+ #define MEMORY_END_CORRECTION 0x200
+
+ char *free_memory_start = heaplimit,
+ *free_memory_end = stacklimit - MEMORY_END_CORRECTION;
+
+#elif defined(__SAM3X8E__)
+
+ extern char _ebss;
+
+ char *end_bss = &_ebss,
+ *free_memory_start = end_bss,
+ *free_memory_end = 0,
+ *stacklimit = 0,
+ *heaplimit = 0;
+
+ #define MEMORY_END_CORRECTION 0x10000 // need to stay well below 0x20080000 or M100 F crashes
+
+#elif defined(__SAMD51__)
+
+ extern unsigned int __bss_end__, __StackLimit, __HeapLimit;
+ extern "C" void * _sbrk(int incr);
+
+ void *end_bss = &__bss_end__,
+ *stacklimit = &__StackLimit,
+ *heaplimit = &__HeapLimit;
+
+ #define MEMORY_END_CORRECTION 0x400
+
+ char *free_memory_start = (char *)_sbrk(0) + 0x200, // Leave some heap space
+ *free_memory_end = (char *)stacklimit - MEMORY_END_CORRECTION;
+
+#else
+ #error "M100 - unsupported CPU"
+#endif
+
+//
+// Utility functions
+//
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wreturn-local-addr"
+
+// Location of a variable in its stack frame.
+// The returned address will be above the stack (after it returns).
+char *top_of_stack() {
+ char x;
+ return &x + 1; // x is pulled on return;
+}
+
+#pragma GCC diagnostic pop
+
+// Count the number of test bytes at the specified location.
+inline int32_t count_test_bytes(const char * const start_free_memory) {
+ for (uint32_t i = 0; i < 32000; i++)
+ if (char(start_free_memory[i]) != TEST_BYTE)
+ return i - 1;
+
+ return -1;
+}
+
+//
+// M100 sub-commands
+//
+
+#if ENABLED(M100_FREE_MEMORY_DUMPER)
+ /**
+ * M100 D
+ * Dump the free memory block from brkval to the stack pointer.
+ * malloc() eats memory from the start of the block and the stack grows
+ * up from the bottom of the block. Solid test bytes indicate nothing has
+ * used that memory yet. There should not be anything but test bytes within
+ * the block. If so, it may indicate memory corruption due to a bad pointer.
+ * Unexpected bytes are flagged in the right column.
+ */
+ inline void dump_free_memory(char *start_free_memory, char *end_free_memory) {
+ //
+ // Start and end the dump on a nice 16 byte boundary
+ // (even though the values are not 16-byte aligned).
+ //
+ start_free_memory = (char*)(uintptr_t(uint32_t(start_free_memory) & ~0xFUL)); // Align to 16-byte boundary
+ end_free_memory = (char*)(uintptr_t(uint32_t(end_free_memory) | 0xFUL)); // Align end_free_memory to the 15th byte (at or above end_free_memory)
+
+ // Dump command main loop
+ while (start_free_memory < end_free_memory) {
+ print_hex_address(start_free_memory); // Print the address
+ SERIAL_CHAR(':');
+ LOOP_L_N(i, 16) { // and 16 data bytes
+ if (i == 8) SERIAL_CHAR('-');
+ print_hex_byte(start_free_memory[i]);
+ SERIAL_CHAR(' ');
+ }
+ serial_delay(25);
+ SERIAL_CHAR('|'); // Point out non test bytes
+ LOOP_L_N(i, 16) {
+ char ccc = (char)start_free_memory[i]; // cast to char before automatically casting to char on assignment, in case the compiler is broken
+ ccc = (ccc == TEST_BYTE) ? ' ' : '?';
+ SERIAL_CHAR(ccc);
+ }
+ SERIAL_EOL();
+ start_free_memory += 16;
+ serial_delay(25);
+ idle();
+ }
+ }
+
+ void M100_dump_routine(PGM_P const title, const char * const start, const char * const end) {
+ serialprintPGM(title);
+ SERIAL_EOL();
+ //
+ // Round the start and end locations to produce full lines of output
+ //
+ dump_free_memory(
+ (char*)(uintptr_t(uint32_t(start) & ~0xFUL)), // Align to 16-byte boundary
+ (char*)(uintptr_t(uint32_t(end) | 0xFUL)) // Align end_free_memory to the 15th byte (at or above end_free_memory)
+ );
+ }
+
+#endif // M100_FREE_MEMORY_DUMPER
+
+inline int check_for_free_memory_corruption(PGM_P const title) {
+ serialprintPGM(title);
+
+ char *start_free_memory = free_memory_start, *end_free_memory = free_memory_end;
+ int n = end_free_memory - start_free_memory;
+
+ SERIAL_ECHOPAIR("\nfmc() n=", n);
+ SERIAL_ECHOPAIR("\nfree_memory_start=", hex_address(free_memory_start));
+ SERIAL_ECHOLNPAIR(" end_free_memory=", hex_address(end_free_memory));
+
+ if (end_free_memory < start_free_memory) {
+ SERIAL_ECHOPGM(" end_free_memory < Heap ");
+ // SET_INPUT_PULLUP(63); // if the developer has a switch wired up to their controller board
+ // safe_delay(5); // this code can be enabled to pause the display as soon as the
+ // while ( READ(63)) // malfunction is detected. It is currently defaulting to a switch
+ // idle(); // being on pin-63 which is unassigend and available on most controller
+ // safe_delay(20); // boards.
+ // while ( !READ(63))
+ // idle();
+ serial_delay(20);
+ #if ENABLED(M100_FREE_MEMORY_DUMPER)
+ M100_dump_routine(PSTR(" Memory corruption detected with end_free_memory<Heap\n"), (const char*)0x1B80, (const char*)0x21FF);
+ #endif
+ }
+
+ // Scan through the range looking for the biggest block of 0xE5's we can find
+ int block_cnt = 0;
+ for (int i = 0; i < n; i++) {
+ if (start_free_memory[i] == TEST_BYTE) {
+ int32_t j = count_test_bytes(start_free_memory + i);
+ if (j > 8) {
+ // SERIAL_ECHOPAIR("Found ", j);
+ // SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(start_free_memory + i));
+ i += j;
+ block_cnt++;
+ SERIAL_ECHOPAIR(" (", block_cnt);
+ SERIAL_ECHOPAIR(") found=", j);
+ SERIAL_ECHOLNPGM(" ");
+ }
+ }
+ }
+ SERIAL_ECHOPAIR(" block_found=", block_cnt);
+
+ if (block_cnt != 1)
+ SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area.");
+
+ if (block_cnt == 0) // Make sure the special case of no free blocks shows up as an
+ block_cnt = -1; // error to the calling code!
+
+ SERIAL_ECHOPGM(" return=");
+ if (block_cnt == 1) {
+ SERIAL_CHAR('0'); // If the block_cnt is 1, nothing has broken up the free memory
+ SERIAL_EOL(); // area and it is appropriate to say 'no corruption'.
+ return 0;
+ }
+ SERIAL_ECHOLNPGM("true");
+ return block_cnt;
+}
+
+/**
+ * M100 F
+ * Return the number of free bytes in the memory pool,
+ * with other vital statistics defining the pool.
+ */
+inline void free_memory_pool_report(char * const start_free_memory, const int32_t size) {
+ int32_t max_cnt = -1, block_cnt = 0;
+ char *max_addr = nullptr;
+ // Find the longest block of test bytes in the buffer
+ for (int32_t i = 0; i < size; i++) {
+ char *addr = start_free_memory + i;
+ if (*addr == TEST_BYTE) {
+ const int32_t j = count_test_bytes(addr);
+ if (j > 8) {
+ SERIAL_ECHOPAIR("Found ", j);
+ SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(addr));
+ if (j > max_cnt) {
+ max_cnt = j;
+ max_addr = addr;
+ }
+ i += j;
+ block_cnt++;
+ }
+ }
+ }
+ if (block_cnt > 1) {
+ SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area.");
+ SERIAL_ECHOPAIR("\nLargest free block is ", max_cnt);
+ SERIAL_ECHOLNPAIR(" bytes at ", hex_address(max_addr));
+ }
+ SERIAL_ECHOLNPAIR("check_for_free_memory_corruption() = ", check_for_free_memory_corruption(PSTR("M100 F ")));
+}
+
+#if ENABLED(M100_FREE_MEMORY_CORRUPTOR)
+ /**
+ * M100 C<num>
+ * Corrupt <num> locations in the free memory pool and report the corrupt addresses.
+ * This is useful to check the correctness of the M100 D and the M100 F commands.
+ */
+ inline void corrupt_free_memory(char *start_free_memory, const uint32_t size) {
+ start_free_memory += 8;
+ const uint32_t near_top = top_of_stack() - start_free_memory - 250, // -250 to avoid interrupt activity that's altered the stack.
+ j = near_top / (size + 1);
+
+ SERIAL_ECHOLNPGM("Corrupting free memory block.\n");
+ for (uint32_t i = 1; i <= size; i++) {
+ char * const addr = start_free_memory + i * j;
+ *addr = i;
+ SERIAL_ECHOPAIR("\nCorrupting address: ", hex_address(addr));
+ }
+ SERIAL_EOL();
+ }
+#endif // M100_FREE_MEMORY_CORRUPTOR
+
+/**
+ * M100 I
+ * Init memory for the M100 tests. (Automatically applied on the first M100.)
+ */
+inline void init_free_memory(char *start_free_memory, int32_t size) {
+ SERIAL_ECHOLNPGM("Initializing free memory block.\n\n");
+
+ size -= 250; // -250 to avoid interrupt activity that's altered the stack.
+ if (size < 0) {
+ SERIAL_ECHOLNPGM("Unable to initialize.\n");
+ return;
+ }
+
+ start_free_memory += 8; // move a few bytes away from the heap just because we don't want
+ // to be altering memory that close to it.
+ memset(start_free_memory, TEST_BYTE, size);
+
+ SERIAL_ECHO(size);
+ SERIAL_ECHOLNPGM(" bytes of memory initialized.\n");
+
+ for (int32_t i = 0; i < size; i++) {
+ if (start_free_memory[i] != TEST_BYTE) {
+ SERIAL_ECHOPAIR("? address : ", hex_address(start_free_memory + i));
+ SERIAL_ECHOLNPAIR("=", hex_byte(start_free_memory[i]));
+ SERIAL_EOL();
+ }
+ }
+}
+
+/**
+ * M100: Free Memory Check
+ */
+void GcodeSuite::M100() {
+
+ char *sp = top_of_stack();
+ if (!free_memory_end) free_memory_end = sp - MEMORY_END_CORRECTION;
+ SERIAL_ECHOPAIR("\nbss_end : ", hex_address(end_bss));
+ if (heaplimit) SERIAL_ECHOPAIR("\n__heaplimit : ", hex_address(heaplimit));
+ SERIAL_ECHOPAIR("\nfree_memory_start : ", hex_address(free_memory_start));
+ if (stacklimit) SERIAL_ECHOPAIR("\n__stacklimit : ", hex_address(stacklimit));
+ SERIAL_ECHOPAIR("\nfree_memory_end : ", hex_address(free_memory_end));
+ if (MEMORY_END_CORRECTION) SERIAL_ECHOPAIR("\nMEMORY_END_CORRECTION: ", MEMORY_END_CORRECTION);
+ SERIAL_ECHOLNPAIR("\nStack Pointer : ", hex_address(sp));
+
+ // Always init on the first invocation of M100
+ static bool m100_not_initialized = true;
+ if (m100_not_initialized || parser.seen('I')) {
+ m100_not_initialized = false;
+ init_free_memory(free_memory_start, free_memory_end - free_memory_start);
+ }
+
+ #if ENABLED(M100_FREE_MEMORY_DUMPER)
+ if (parser.seen('D'))
+ return dump_free_memory(free_memory_start, free_memory_end);
+ #endif
+
+ if (parser.seen('F'))
+ return free_memory_pool_report(free_memory_start, free_memory_end - free_memory_start);
+
+ #if ENABLED(M100_FREE_MEMORY_CORRUPTOR)
+
+ if (parser.seen('C'))
+ return corrupt_free_memory(free_memory_start, parser.value_int());
+
+ #endif
+}
+
+#endif // M100_FREE_MEMORY_WATCHER
diff --git a/Marlin/src/gcode/calibrate/M12.cpp b/Marlin/src/gcode/calibrate/M12.cpp
new file mode 100644
index 0000000..da24454
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/M12.cpp
@@ -0,0 +1,39 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER)
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+#include "../../feature/closedloop.h"
+
+void GcodeSuite::M12() {
+
+ planner.synchronize();
+
+ if (parser.seenval('S'))
+ closedloop.set(parser.value_int()); // Force a CLC set
+
+}
+
+#endif
diff --git a/Marlin/src/gcode/calibrate/M425.cpp b/Marlin/src/gcode/calibrate/M425.cpp
new file mode 100644
index 0000000..40441ac
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/M425.cpp
@@ -0,0 +1,111 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(BACKLASH_GCODE)
+
+#include "../../feature/backlash.h"
+#include "../../module/planner.h"
+
+#include "../gcode.h"
+
+/**
+ * M425: Enable and tune backlash correction.
+ *
+ * F<fraction> Enable/disable/fade-out backlash correction (0.0 to 1.0)
+ * S<smoothing_mm> Distance over which backlash correction is spread
+ * X<distance_mm> Set the backlash distance on X (0 to disable)
+ * Y<distance_mm> ... on Y
+ * Z<distance_mm> ... on Z
+ * X If a backlash measurement was done on X, copy that value
+ * Y ... on Y
+ * Z ... on Z
+ *
+ * Type M425 without any arguments to show active values.
+ */
+void GcodeSuite::M425() {
+ bool noArgs = true;
+
+ auto axis_can_calibrate = [](const uint8_t a) {
+ switch (a) {
+ default:
+ case X_AXIS: return AXIS_CAN_CALIBRATE(X);
+ case Y_AXIS: return AXIS_CAN_CALIBRATE(Y);
+ case Z_AXIS: return AXIS_CAN_CALIBRATE(Z);
+ }
+ };
+
+ LOOP_XYZ(a) {
+ if (axis_can_calibrate(a) && parser.seen(XYZ_CHAR(a))) {
+ planner.synchronize();
+ backlash.distance_mm[a] = parser.has_value() ? parser.value_linear_units() : backlash.get_measurement(AxisEnum(a));
+ noArgs = false;
+ }
+ }
+
+ if (parser.seen('F')) {
+ planner.synchronize();
+ backlash.set_correction(parser.value_float());
+ noArgs = false;
+ }
+
+ #ifdef BACKLASH_SMOOTHING_MM
+ if (parser.seen('S')) {
+ planner.synchronize();
+ backlash.smoothing_mm = parser.value_linear_units();
+ noArgs = false;
+ }
+ #endif
+
+ if (noArgs) {
+ SERIAL_ECHOPGM("Backlash Correction ");
+ if (!backlash.correction) SERIAL_ECHOPGM("in");
+ SERIAL_ECHOLNPGM("active:");
+ SERIAL_ECHOLNPAIR(" Correction Amount/Fade-out: F", backlash.get_correction(), " (F1.0 = full, F0.0 = none)");
+ SERIAL_ECHOPGM(" Backlash Distance (mm): ");
+ LOOP_XYZ(a) if (axis_can_calibrate(a)) {
+ SERIAL_CHAR(' ', XYZ_CHAR(a));
+ SERIAL_ECHO(backlash.distance_mm[a]);
+ SERIAL_EOL();
+ }
+
+ #ifdef BACKLASH_SMOOTHING_MM
+ SERIAL_ECHOLNPAIR(" Smoothing (mm): S", backlash.smoothing_mm);
+ #endif
+
+ #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+ SERIAL_ECHOPGM(" Average measured backlash (mm):");
+ if (backlash.has_any_measurement()) {
+ LOOP_XYZ(a) if (axis_can_calibrate(a) && backlash.has_measurement(AxisEnum(a))) {
+ SERIAL_CHAR(' ', XYZ_CHAR(a));
+ SERIAL_ECHO(backlash.get_measurement(AxisEnum(a)));
+ }
+ }
+ else
+ SERIAL_ECHOPGM(" (Not yet measured)");
+ SERIAL_EOL();
+ #endif
+ }
+}
+
+#endif // BACKLASH_GCODE
diff --git a/Marlin/src/gcode/calibrate/M48.cpp b/Marlin/src/gcode/calibrate/M48.cpp
new file mode 100644
index 0000000..97aea59
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/M48.cpp
@@ -0,0 +1,275 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/probe.h"
+#include "../../lcd/marlinui.h"
+
+#include "../../feature/bedlevel/bedlevel.h"
+
+#if HAS_LEVELING
+ #include "../../module/planner.h"
+#endif
+
+/**
+ * M48: Z probe repeatability measurement function.
+ *
+ * Usage:
+ * M48 <P#> <X#> <Y#> <V#> <E> <L#> <S>
+ * 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.
+ */
+
+void GcodeSuite::M48() {
+
+ if (homing_needed_error()) return;
+
+ const int8_t verbose_level = parser.byteval('V', 1);
+ if (!WITHIN(verbose_level, 0, 4)) {
+ SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).");
+ return;
+ }
+
+ if (verbose_level > 0)
+ SERIAL_ECHOLNPGM("M48 Z-Probe Repeatability Test");
+
+ const int8_t n_samples = parser.byteval('P', 10);
+ if (!WITHIN(n_samples, 4, 50)) {
+ SERIAL_ECHOLNPGM("?Sample size not plausible (4-50).");
+ return;
+ }
+
+ const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE;
+
+ // Test at the current position by default, overridden by X and Y
+ const xy_pos_t test_position = {
+ parser.linearval('X', current_position.x + probe.offset_xy.x), // If no X use the probe's current X position
+ parser.linearval('Y', current_position.y + probe.offset_xy.y) // If no Y, ditto
+ };
+
+ if (!probe.can_reach(test_position)) {
+ ui.set_status_P(GET_TEXT(MSG_M48_OUT_OF_BOUNDS), 99);
+ SERIAL_ECHOLNPGM("? (X,Y) out of bounds.");
+ return;
+ }
+
+ // Get the number of leg moves per test-point
+ bool seen_L = parser.seen('L');
+ uint8_t n_legs = seen_L ? parser.value_byte() : 0;
+ if (n_legs > 15) {
+ SERIAL_ECHOLNPGM("?Legs of movement implausible (0-15).");
+ return;
+ }
+ if (n_legs == 1) n_legs = 2;
+
+ // Schizoid motion as an optional stress-test
+ const bool schizoid_flag = parser.boolval('S');
+ if (schizoid_flag && !seen_L) n_legs = 7;
+
+ if (verbose_level > 2)
+ SERIAL_ECHOLNPGM("Positioning the probe...");
+
+ // Always disable Bed Level correction before probing...
+
+ #if HAS_LEVELING
+ const bool was_enabled = planner.leveling_active;
+ set_bed_leveling_enabled(false);
+ #endif
+
+ // Work with reasonable feedrates
+ remember_feedrate_scaling_off();
+
+ // Working variables
+ float mean = 0.0, // The average of all points so far, used to calculate deviation
+ sigma = 0.0, // Standard deviation of all points so far
+ min = 99999.9, // Smallest value sampled so far
+ max = -99999.9, // Largest value sampled so far
+ sample_set[n_samples]; // Storage for sampled values
+
+ auto dev_report = [](const bool verbose, const float &mean, const float &sigma, const float &min, const float &max, const bool final=false) {
+ if (verbose) {
+ SERIAL_ECHOPAIR_F("Mean: ", mean, 6);
+ if (!final) SERIAL_ECHOPAIR_F(" Sigma: ", sigma, 6);
+ SERIAL_ECHOPAIR_F(" Min: ", min, 3);
+ SERIAL_ECHOPAIR_F(" Max: ", max, 3);
+ SERIAL_ECHOPAIR_F(" Range: ", max-min, 3);
+ if (final) SERIAL_EOL();
+ }
+ if (final) {
+ SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6);
+ SERIAL_EOL();
+ }
+ };
+
+ // Move to the first point, deploy, and probe
+ const float t = probe.probe_at_point(test_position, raise_after, verbose_level);
+ bool probing_good = !isnan(t);
+
+ if (probing_good) {
+ randomSeed(millis());
+
+ float sample_sum = 0.0;
+
+ LOOP_L_N(n, n_samples) {
+ #if HAS_WIRED_LCD
+ // Display M48 progress in the status bar
+ ui.status_printf_P(0, PSTR(S_FMT ": %d/%d"), GET_TEXT(MSG_M48_POINT), int(n + 1), int(n_samples));
+ #endif
+
+ // When there are "legs" of movement move around the point before probing
+ if (n_legs) {
+
+ // Pick a random direction, starting angle, and radius
+ const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise
+ float angle = random(0, 360);
+ const float radius = random(
+ #if ENABLED(DELTA)
+ int(0.1250000000 * (DELTA_PRINTABLE_RADIUS)),
+ int(0.3333333333 * (DELTA_PRINTABLE_RADIUS))
+ #else
+ int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE))
+ #endif
+ );
+ if (verbose_level > 3) {
+ SERIAL_ECHOPAIR("Start radius:", radius, " angle:", angle, " dir:");
+ if (dir > 0) SERIAL_CHAR('C');
+ SERIAL_ECHOLNPGM("CW");
+ }
+
+ // Move from leg to leg in rapid succession
+ LOOP_L_N(l, n_legs - 1) {
+
+ // Move some distance around the perimeter
+ float delta_angle;
+ if (schizoid_flag) {
+ // The points of a 5 point star are 72 degrees apart.
+ // Skip a point and go to the next one on the star.
+ delta_angle = dir * 2.0 * 72.0;
+ }
+ else {
+ // Just move further along the perimeter.
+ delta_angle = dir * (float)random(25, 45);
+ }
+ angle += delta_angle;
+
+ // Trig functions work without clamping, but just to be safe...
+ while (angle > 360.0) angle -= 360.0;
+ while (angle < 0.0) angle += 360.0;
+
+ // Choose the next position as an offset to chosen test position
+ const xy_pos_t noz_pos = test_position - probe.offset_xy;
+ xy_pos_t next_pos = {
+ noz_pos.x + float(cos(RADIANS(angle))) * radius,
+ noz_pos.y + float(sin(RADIANS(angle))) * radius
+ };
+
+ #if ENABLED(DELTA)
+ // If the probe can't reach the point on a round bed...
+ // Simply scale the numbers to bring them closer to origin.
+ while (!probe.can_reach(next_pos)) {
+ next_pos *= 0.8f;
+ if (verbose_level > 3)
+ SERIAL_ECHOLNPAIR_P(PSTR("Moving inward: X"), next_pos.x, SP_Y_STR, next_pos.y);
+ }
+ #else
+ // For a rectangular bed just keep the probe in bounds
+ LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS);
+ LIMIT(next_pos.y, Y_MIN_POS, Y_MAX_POS);
+ #endif
+
+ if (verbose_level > 3)
+ SERIAL_ECHOLNPAIR_P(PSTR("Going to: X"), next_pos.x, SP_Y_STR, next_pos.y);
+
+ do_blocking_move_to_xy(next_pos);
+ } // n_legs loop
+ } // n_legs
+
+ // Probe a single point
+ const float pz = probe.probe_at_point(test_position, raise_after, 0);
+
+ // Break the loop if the probe fails
+ probing_good = !isnan(pz);
+ if (!probing_good) break;
+
+ // Store the new sample
+ sample_set[n] = pz;
+
+ // Keep track of the largest and smallest samples
+ NOMORE(min, pz);
+ NOLESS(max, pz);
+
+ // Get the mean value of all samples thus far
+ sample_sum += pz;
+ mean = sample_sum / (n + 1);
+
+ // Calculate the standard deviation so far.
+ // The value after the last sample will be the final output.
+ float dev_sum = 0.0;
+ LOOP_LE_N(j, n) dev_sum += sq(sample_set[j] - mean);
+ sigma = SQRT(dev_sum / (n + 1));
+
+ if (verbose_level > 1) {
+ SERIAL_ECHO((int)(n + 1));
+ SERIAL_ECHOPAIR(" of ", (int)n_samples);
+ SERIAL_ECHOPAIR_F(": z: ", pz, 3);
+ SERIAL_CHAR(' ');
+ dev_report(verbose_level > 2, mean, sigma, min, max);
+ SERIAL_EOL();
+ }
+
+ } // n_samples loop
+ }
+
+ probe.stow();
+
+ if (probing_good) {
+ SERIAL_ECHOLNPGM("Finished!");
+ dev_report(verbose_level > 0, mean, sigma, min, max, true);
+
+ #if HAS_WIRED_LCD
+ // Display M48 results in the status bar
+ char sigma_str[8];
+ ui.status_printf_P(0, PSTR(S_FMT ": %s"), GET_TEXT(MSG_M48_DEVIATION), dtostrf(sigma, 2, 6, sigma_str));
+ #endif
+ }
+
+ restore_feedrate_and_scaling();
+
+ // Re-enable bed level correction if it had been on
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(was_enabled));
+
+ report_current_position();
+}
+
+#endif // Z_MIN_PROBE_REPEATABILITY_TEST
diff --git a/Marlin/src/gcode/calibrate/M665.cpp b/Marlin/src/gcode/calibrate/M665.cpp
new file mode 100644
index 0000000..557204c
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/M665.cpp
@@ -0,0 +1,112 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if IS_KINEMATIC
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+
+#if ENABLED(DELTA)
+
+ #include "../../module/delta.h"
+ /**
+ * M665: Set delta configurations
+ *
+ * H = delta height
+ * L = diagonal rod
+ * R = delta radius
+ * S = segments per second
+ * X = Alpha (Tower 1) angle trim
+ * Y = Beta (Tower 2) angle trim
+ * Z = Gamma (Tower 3) angle trim
+ * A = Alpha (Tower 1) digonal rod trim
+ * B = Beta (Tower 2) digonal rod trim
+ * C = Gamma (Tower 3) digonal rod trim
+ */
+ void GcodeSuite::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('X')) delta_tower_angle_trim.a = parser.value_float();
+ if (parser.seen('Y')) delta_tower_angle_trim.b = parser.value_float();
+ if (parser.seen('Z')) delta_tower_angle_trim.c = parser.value_float();
+ if (parser.seen('A')) delta_diagonal_rod_trim.a = parser.value_float();
+ if (parser.seen('B')) delta_diagonal_rod_trim.b = parser.value_float();
+ if (parser.seen('C')) delta_diagonal_rod_trim.c = parser.value_float();
+ recalc_delta_settings();
+ }
+
+#elif IS_SCARA
+
+ #include "../../module/scara.h"
+
+ /**
+ * 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
+ * Z[z-offset] - Z offset, added to Z
+ *
+ * A, P, and X are all aliases for the shoulder angle
+ * B, T, and Y are all aliases for the elbow angle
+ */
+ void GcodeSuite::M665() {
+ if (parser.seenval('S')) delta_segments_per_second = parser.value_float();
+
+ #if HAS_SCARA_OFFSET
+
+ if (parser.seenval('Z')) scara_home_offset.z = parser.value_linear_units();
+
+ const bool hasA = parser.seenval('A'), hasP = parser.seenval('P'), hasX = parser.seenval('X');
+ const uint8_t sumAPX = hasA + hasP + hasX;
+ if (sumAPX) {
+ if (sumAPX == 1)
+ scara_home_offset.a = parser.value_float();
+ else {
+ SERIAL_ERROR_MSG("Only one of A, P, or X is allowed.");
+ return;
+ }
+ }
+
+ const bool hasB = parser.seenval('B'), hasT = parser.seenval('T'), hasY = parser.seenval('Y');
+ const uint8_t sumBTY = hasB + hasT + hasY;
+ if (sumBTY) {
+ if (sumBTY == 1)
+ scara_home_offset.b = parser.value_float();
+ else {
+ SERIAL_ERROR_MSG("Only one of B, T, or Y is allowed.");
+ return;
+ }
+ }
+
+ #endif // HAS_SCARA_OFFSET
+ }
+
+#endif
+
+#endif // IS_KINEMATIC
diff --git a/Marlin/src/gcode/calibrate/M666.cpp b/Marlin/src/gcode/calibrate/M666.cpp
new file mode 100644
index 0000000..e915aa8
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/M666.cpp
@@ -0,0 +1,105 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(DELTA) || HAS_EXTRA_ENDSTOPS
+
+#include "../gcode.h"
+
+#if ENABLED(DELTA)
+
+ #include "../../module/delta.h"
+ #include "../../module/motion.h"
+
+ #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
+ #include "../../core/debug_out.h"
+
+ /**
+ * M666: Set delta endstop adjustment
+ */
+ void GcodeSuite::M666() {
+ DEBUG_SECTION(log_M666, "M666", DEBUGGING(LEVELING));
+ LOOP_XYZ(i) {
+ if (parser.seen(XYZ_CHAR(i))) {
+ const float v = parser.value_linear_units();
+ if (v * Z_HOME_DIR <= 0) delta_endstop_adj[i] = v;
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("delta_endstop_adj[", XYZ_CHAR(i), "] = ", delta_endstop_adj[i]);
+ }
+ }
+ }
+
+#elif HAS_EXTRA_ENDSTOPS
+
+ #include "../../module/endstops.h"
+
+ /**
+ * M666: Set Dual Endstops offsets for X, Y, and/or Z.
+ * With no parameters report current offsets.
+ *
+ * For Triple / Quad Z Endstops:
+ * Set Z2 Only: M666 S2 Z<offset>
+ * Set Z3 Only: M666 S3 Z<offset>
+ * Set Z4 Only: M666 S4 Z<offset>
+ * Set All: M666 Z<offset>
+ */
+ void GcodeSuite::M666() {
+ #if ENABLED(X_DUAL_ENDSTOPS)
+ if (parser.seenval('X')) endstops.x2_endstop_adj = parser.value_linear_units();
+ #endif
+ #if ENABLED(Y_DUAL_ENDSTOPS)
+ if (parser.seenval('Y')) endstops.y2_endstop_adj = parser.value_linear_units();
+ #endif
+ #if ENABLED(Z_MULTI_ENDSTOPS)
+ if (parser.seenval('Z')) {
+ #if NUM_Z_STEPPER_DRIVERS >= 3
+ const float z_adj = parser.value_linear_units();
+ const int ind = parser.intval('S');
+ if (!ind || ind == 2) endstops.z2_endstop_adj = z_adj;
+ if (!ind || ind == 3) endstops.z3_endstop_adj = z_adj;
+ #if NUM_Z_STEPPER_DRIVERS >= 4
+ if (!ind || ind == 4) endstops.z4_endstop_adj = z_adj;
+ #endif
+ #else
+ endstops.z2_endstop_adj = parser.value_linear_units();
+ #endif
+ }
+ #endif
+ if (!parser.seen("XYZ")) {
+ SERIAL_ECHOPGM("Dual Endstop Adjustment (mm): ");
+ #if ENABLED(X_DUAL_ENDSTOPS)
+ SERIAL_ECHOPAIR(" X2:", endstops.x2_endstop_adj);
+ #endif
+ #if ENABLED(Y_DUAL_ENDSTOPS)
+ SERIAL_ECHOPAIR(" Y2:", endstops.y2_endstop_adj);
+ #endif
+ #if ENABLED(Z_MULTI_ENDSTOPS)
+ #define _ECHO_ZADJ(N) SERIAL_ECHOPAIR(" Z" STRINGIFY(N) ":", endstops.z##N##_endstop_adj);
+ REPEAT_S(2, INCREMENT(NUM_Z_STEPPER_DRIVERS), _ECHO_ZADJ)
+ #endif
+ SERIAL_EOL();
+ }
+ }
+
+#endif // HAS_EXTRA_ENDSTOPS
+
+#endif // DELTA || HAS_EXTRA_ENDSTOPS
diff --git a/Marlin/src/gcode/calibrate/M852.cpp b/Marlin/src/gcode/calibrate/M852.cpp
new file mode 100644
index 0000000..b60f417
--- /dev/null
+++ b/Marlin/src/gcode/calibrate/M852.cpp
@@ -0,0 +1,106 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SKEW_CORRECTION_GCODE)
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+
+/**
+ * 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
+ */
+void GcodeSuite::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.skew_factor.xy != value) {
+ planner.skew_factor.xy = 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.skew_factor.xz != value) {
+ planner.skew_factor.xz = 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.skew_factor.yz != value) {
+ planner.skew_factor.yz = value;
+ ++setval;
+ }
+ }
+ else
+ ++badval;
+ }
+
+ #endif
+
+ if (badval)
+ SERIAL_ECHOLNPGM(STR_SKEW_MIN " " STRINGIFY(SKEW_FACTOR_MIN) " " STR_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();
+ report_current_position();
+ }
+
+ if (!ijk) {
+ SERIAL_ECHO_START();
+ serialprintPGM(GET_TEXT(MSG_SKEW_FACTOR));
+ SERIAL_ECHOPAIR_F(" XY: ", planner.skew_factor.xy, 6);
+ #if ENABLED(SKEW_CORRECTION_FOR_Z)
+ SERIAL_ECHOPAIR_F(" XZ: ", planner.skew_factor.xz, 6);
+ SERIAL_ECHOPAIR_F(" YZ: ", planner.skew_factor.yz, 6);
+ #endif
+ SERIAL_EOL();
+ }
+}
+
+#endif // SKEW_CORRECTION_GCODE
diff --git a/Marlin/src/gcode/config/M200-M205.cpp b/Marlin/src/gcode/config/M200-M205.cpp
new file mode 100644
index 0000000..cb17fc4
--- /dev/null
+++ b/Marlin/src/gcode/config/M200-M205.cpp
@@ -0,0 +1,191 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../MarlinCore.h"
+#include "../../module/planner.h"
+
+#if DISABLED(NO_VOLUMETRICS)
+
+ /**
+ * M200: Set filament diameter and set E axis units to cubic units
+ *
+ * T<extruder> - Optional extruder number. Current extruder if omitted.
+ * D<linear> - Set filament diameter and enable. D0 disables volumetric.
+ * S<bool> - Turn volumetric ON or OFF.
+ * L<float> - Volumetric extruder limit (in mm^3/sec). L0 disables the limit.
+ */
+ void GcodeSuite::M200() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ bool vol_enable = parser.volumetric_enabled,
+ can_enable = true;
+
+ if (parser.seenval('D')) {
+ const float dval = parser.value_linear_units();
+ if (dval) { // Set filament size for volumetric calculation
+ planner.set_filament_size(target_extruder, dval);
+ vol_enable = true; // Dn = enable for compatibility
+ }
+ else
+ can_enable = false; // D0 = disable for compatibility
+ }
+
+ // Enable or disable with S1 / S0
+ parser.volumetric_enabled = can_enable && parser.boolval('S', vol_enable);
+
+ #if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT)
+ if (parser.seenval('L')) {
+ // Set volumetric limit (in mm^3/sec)
+ const float lval = parser.value_float();
+ if (WITHIN(lval, 0, 20))
+ planner.set_volumetric_extruder_limit(target_extruder, lval);
+ else
+ SERIAL_ECHOLNPGM("?L value out of range (0-20).");
+ }
+ #endif
+
+ 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.
+ */
+void GcodeSuite::M201() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ #ifdef XY_FREQUENCY_LIMIT
+ if (parser.seenval('F')) planner.set_frequency_limit(parser.value_byte());
+ if (parser.seenval('G')) planner.xy_freq_min_speed_factor = constrain(parser.value_float(), 1, 100) / 100;
+ #endif
+
+ LOOP_XYZE(i) {
+ if (parser.seen(axis_codes[i])) {
+ const uint8_t a = (i == E_AXIS ? uint8_t(E_AXIS_N(target_extruder)) : i);
+ planner.set_max_acceleration(a, parser.value_axis_units((AxisEnum)a));
+ }
+ }
+}
+
+/**
+ * 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.
+ */
+void GcodeSuite::M203() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ LOOP_XYZE(i)
+ if (parser.seen(axis_codes[i])) {
+ const uint8_t a = (i == E_AXIS ? uint8_t(E_AXIS_N(target_extruder)) : i);
+ planner.set_max_feedrate(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
+ */
+void GcodeSuite::M204() {
+ if (!parser.seen("PRST")) {
+ SERIAL_ECHOPAIR("Acceleration: P", planner.settings.acceleration);
+ SERIAL_ECHOPAIR(" R", planner.settings.retract_acceleration);
+ SERIAL_ECHOLNPAIR_P(SP_T_STR, planner.settings.travel_acceleration);
+ }
+ else {
+ //planner.synchronize();
+ // 'S' for legacy compatibility. Should NOT BE USED for new development
+ if (parser.seenval('S')) planner.settings.travel_acceleration = planner.settings.acceleration = parser.value_linear_units();
+ if (parser.seenval('P')) planner.settings.acceleration = parser.value_linear_units();
+ if (parser.seenval('R')) planner.settings.retract_acceleration = parser.value_linear_units();
+ if (parser.seenval('T')) planner.settings.travel_acceleration = parser.value_linear_units();
+ }
+}
+
+/**
+ * M205: Set Advanced Settings
+ *
+ * B = Min Segment Time (µs)
+ * S = Min Feed Rate (units/s)
+ * T = Min Travel Feed Rate (units/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)
+ * J = Junction Deviation (mm) (If not using CLASSIC_JERK)
+ */
+void GcodeSuite::M205() {
+ #if HAS_JUNCTION_DEVIATION
+ #define J_PARAM "J"
+ #else
+ #define J_PARAM
+ #endif
+ #if HAS_CLASSIC_JERK
+ #define XYZE_PARAM "XYZE"
+ #else
+ #define XYZE_PARAM
+ #endif
+ if (!parser.seen("BST" J_PARAM XYZE_PARAM)) return;
+
+ //planner.synchronize();
+ if (parser.seen('B')) planner.settings.min_segment_time_us = parser.value_ulong();
+ if (parser.seen('S')) planner.settings.min_feedrate_mm_s = parser.value_linear_units();
+ if (parser.seen('T')) planner.settings.min_travel_feedrate_mm_s = parser.value_linear_units();
+ #if HAS_JUNCTION_DEVIATION
+ if (parser.seen('J')) {
+ const float junc_dev = parser.value_linear_units();
+ if (WITHIN(junc_dev, 0.01f, 0.3f)) {
+ planner.junction_deviation_mm = junc_dev;
+ TERN_(LIN_ADVANCE, planner.recalculate_max_e_jerk());
+ }
+ else
+ SERIAL_ERROR_MSG("?J out of range (0.01 to 0.3)");
+ }
+ #endif
+ #if HAS_CLASSIC_JERK
+ if (parser.seen('X')) planner.set_max_jerk(X_AXIS, parser.value_linear_units());
+ if (parser.seen('Y')) planner.set_max_jerk(Y_AXIS, parser.value_linear_units());
+ if (parser.seen('Z')) {
+ planner.set_max_jerk(Z_AXIS, parser.value_linear_units());
+ #if HAS_MESH && DISABLED(LIMITED_JERK_EDITING)
+ if (planner.max_jerk.z <= 0.1f)
+ SERIAL_ECHOLNPGM("WARNING! Low Z Jerk may lead to unwanted pauses.");
+ #endif
+ }
+ #if HAS_CLASSIC_E_JERK
+ if (parser.seen('E')) planner.set_max_jerk(E_AXIS, parser.value_linear_units());
+ #endif
+ #endif
+}
diff --git a/Marlin/src/gcode/config/M217.cpp b/Marlin/src/gcode/config/M217.cpp
new file mode 100644
index 0000000..f2fefb5
--- /dev/null
+++ b/Marlin/src/gcode/config/M217.cpp
@@ -0,0 +1,171 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_MULTI_EXTRUDER
+
+#include "../gcode.h"
+#include "../../module/tool_change.h"
+
+#if ENABLED(TOOLCHANGE_MIGRATION_FEATURE)
+ #include "../../module/motion.h"
+#endif
+
+#include "../../MarlinCore.h" // for SP_X_STR, etc.
+
+void M217_report(const bool eeprom=false) {
+
+ #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
+ serialprintPGM(eeprom ? PSTR(" M217") : PSTR("Toolchange:"));
+ SERIAL_ECHOPAIR(" S", LINEAR_UNIT(toolchange_settings.swap_length));
+ SERIAL_ECHOPAIR_P(SP_B_STR, LINEAR_UNIT(toolchange_settings.extra_resume),
+ SP_E_STR, LINEAR_UNIT(toolchange_settings.extra_prime),
+ SP_P_STR, LINEAR_UNIT(toolchange_settings.prime_speed));
+ SERIAL_ECHOPAIR(" R", LINEAR_UNIT(toolchange_settings.retract_speed),
+ " U", LINEAR_UNIT(toolchange_settings.unretract_speed),
+ " F", toolchange_settings.fan_speed,
+ " G", toolchange_settings.fan_time);
+
+ #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE)
+ SERIAL_ECHOPAIR(" A", int(migration.automode));
+ SERIAL_ECHOPAIR(" L", LINEAR_UNIT(migration.last));
+ #endif
+
+ #if ENABLED(TOOLCHANGE_PARK)
+ SERIAL_ECHOPAIR(" W", LINEAR_UNIT(toolchange_settings.enable_park));
+ SERIAL_ECHOPAIR_P(SP_X_STR, LINEAR_UNIT(toolchange_settings.change_point.x));
+ SERIAL_ECHOPAIR_P(SP_Y_STR, LINEAR_UNIT(toolchange_settings.change_point.y));
+ #endif
+
+ #if ENABLED(TOOLCHANGE_FS_PRIME_FIRST_USED)
+ SERIAL_ECHOPAIR(" V", LINEAR_UNIT(enable_first_prime));
+ #endif
+
+ #else
+
+ UNUSED(eeprom);
+
+ #endif
+
+ SERIAL_ECHOPAIR_P(SP_Z_STR, LINEAR_UNIT(toolchange_settings.z_raise));
+ SERIAL_EOL();
+}
+
+/**
+ * M217 - Set SINGLENOZZLE toolchange parameters
+ *
+ * // Tool change command
+ * Q Prime active tool and exit
+ *
+ * // Tool change settings
+ * S[linear] Swap length
+ * B[linear] Extra Swap length
+ * E[linear] Prime length
+ * P[linear/m] Prime speed
+ * R[linear/m] Retract speed
+ * U[linear/m] UnRetract speed
+ * V[linear] 0/1 Enable auto prime first extruder used
+ * W[linear] 0/1 Enable park & Z Raise
+ * X[linear] Park X (Requires TOOLCHANGE_PARK)
+ * Y[linear] Park Y (Requires TOOLCHANGE_PARK)
+ * Z[linear] Z Raise
+ * F[linear] Fan Speed 0-255
+ * G[linear/s] Fan time
+ *
+ * Tool migration settings
+ * A[0|1] Enable auto-migration on runout
+ * L[index] Last extruder to use for auto-migration
+ *
+ * Tool migration command
+ * T[index] Migrate to next extruder or the given extruder
+ */
+void GcodeSuite::M217() {
+
+ #if ENABLED(TOOLCHANGE_FILAMENT_SWAP)
+
+ static constexpr float max_extrude = TERN(PREVENT_LENGTHY_EXTRUDE, EXTRUDE_MAXLENGTH, 500);
+
+ if (parser.seen('Q')) { tool_change_prime(); return; }
+
+ if (parser.seenval('S')) { const float v = parser.value_linear_units(); toolchange_settings.swap_length = constrain(v, 0, max_extrude); }
+ if (parser.seenval('B')) { const float v = parser.value_linear_units(); toolchange_settings.extra_resume = constrain(v, -10, 10); }
+ if (parser.seenval('E')) { const float v = parser.value_linear_units(); toolchange_settings.extra_prime = constrain(v, 0, max_extrude); }
+ if (parser.seenval('P')) { const int16_t v = parser.value_linear_units(); toolchange_settings.prime_speed = constrain(v, 10, 5400); }
+ if (parser.seenval('R')) { const int16_t v = parser.value_linear_units(); toolchange_settings.retract_speed = constrain(v, 10, 5400); }
+ if (parser.seenval('U')) { const int16_t v = parser.value_linear_units(); toolchange_settings.unretract_speed = constrain(v, 10, 5400); }
+ #if TOOLCHANGE_FS_FAN >= 0 && HAS_FAN
+ if (parser.seenval('F')) { const int16_t v = parser.value_linear_units(); toolchange_settings.fan_speed = constrain(v, 0, 255); }
+ if (parser.seenval('G')) { const int16_t v = parser.value_linear_units(); toolchange_settings.fan_time = constrain(v, 1, 30); }
+ #endif
+ #endif
+
+ #if ENABLED(TOOLCHANGE_FS_PRIME_FIRST_USED)
+ if (parser.seenval('V')) { enable_first_prime = parser.value_linear_units(); }
+ #endif
+
+ #if ENABLED(TOOLCHANGE_PARK)
+ if (parser.seenval('W')) { toolchange_settings.enable_park = parser.value_linear_units(); }
+ if (parser.seenval('X')) { const int16_t v = parser.value_linear_units(); toolchange_settings.change_point.x = constrain(v, X_MIN_POS, X_MAX_POS); }
+ if (parser.seenval('Y')) { const int16_t v = parser.value_linear_units(); toolchange_settings.change_point.y = constrain(v, Y_MIN_POS, Y_MAX_POS); }
+ #endif
+
+ if (parser.seenval('Z')) { toolchange_settings.z_raise = parser.value_linear_units(); }
+
+ #if ENABLED(TOOLCHANGE_MIGRATION_FEATURE)
+ migration.target = 0; // 0 = disabled
+
+ if (parser.seenval('L')) { // Last
+ const int16_t lval = parser.value_int();
+ if (WITHIN(lval, 0, EXTRUDERS - 1)) {
+ migration.last = lval;
+ migration.automode = (active_extruder < migration.last);
+ }
+ }
+
+ if (parser.seen('A')) // Auto on/off
+ migration.automode = parser.value_bool();
+
+ if (parser.seen('T')) { // Migrate now
+ if (parser.has_value()) {
+ const int16_t tval = parser.value_int();
+ if (WITHIN(tval, 0, EXTRUDERS - 1) && tval != active_extruder) {
+ migration.target = tval + 1;
+ extruder_migration();
+ migration.target = 0; // disable
+ return;
+ }
+ else
+ migration.target = 0; // disable
+ }
+ else {
+ extruder_migration();
+ return;
+ }
+ }
+
+ #endif
+
+ M217_report();
+}
+
+#endif // HAS_MULTI_EXTRUDER
diff --git a/Marlin/src/gcode/config/M218.cpp b/Marlin/src/gcode/config/M218.cpp
new file mode 100644
index 0000000..7701320
--- /dev/null
+++ b/Marlin/src/gcode/config/M218.cpp
@@ -0,0 +1,71 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_HOTEND_OFFSET
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+
+#if ENABLED(DELTA)
+ #include "../../module/planner.h"
+#endif
+
+/**
+ * M218 - set hotend offset (in linear units)
+ *
+ * T<tool>
+ * X<xoffset>
+ * Y<yoffset>
+ * Z<zoffset>
+ */
+void GcodeSuite::M218() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ if (parser.seenval('X')) hotend_offset[target_extruder].x = parser.value_linear_units();
+ if (parser.seenval('Y')) hotend_offset[target_extruder].y = parser.value_linear_units();
+ if (parser.seenval('Z')) hotend_offset[target_extruder].z = parser.value_linear_units();
+
+ if (!parser.seen("XYZ")) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(STR_HOTEND_OFFSET);
+ HOTEND_LOOP() {
+ SERIAL_CHAR(' ');
+ SERIAL_ECHO(hotend_offset[e].x);
+ SERIAL_CHAR(',');
+ SERIAL_ECHO(hotend_offset[e].y);
+ SERIAL_CHAR(',');
+ SERIAL_ECHO_F(hotend_offset[e].z, 3);
+ }
+ SERIAL_EOL();
+ }
+
+ #if ENABLED(DELTA)
+ if (target_extruder == active_extruder)
+ do_blocking_move_to_xy(current_position, planner.settings.max_feedrate_mm_s[X_AXIS]);
+ #endif
+}
+
+#endif // HAS_HOTEND_OFFSET
diff --git a/Marlin/src/gcode/config/M220.cpp b/Marlin/src/gcode/config/M220.cpp
new file mode 100644
index 0000000..75339f1
--- /dev/null
+++ b/Marlin/src/gcode/config/M220.cpp
@@ -0,0 +1,51 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+
+/**
+ * M220: Set speed percentage factor, aka "Feed Rate"
+ *
+ * Parameters
+ * S<percent> : Set the feed rate percentage factor
+ *
+ * Report the current speed percentage factor if no parameter is specified
+ *
+ * For MMU2 and MMU2S devices...
+ * B : Flag to back up the current factor
+ * R : Flag to restore the last-saved factor
+ */
+void GcodeSuite::M220() {
+
+ static int16_t backup_feedrate_percentage = 100;
+ if (parser.seen('B')) backup_feedrate_percentage = feedrate_percentage;
+ if (parser.seen('R')) feedrate_percentage = backup_feedrate_percentage;
+
+ if (parser.seenval('S')) feedrate_percentage = parser.value_int();
+
+ if (!parser.seen_any()) {
+ SERIAL_ECHOPAIR("FR:", feedrate_percentage);
+ SERIAL_CHAR('%');
+ SERIAL_EOL();
+ }
+}
diff --git a/Marlin/src/gcode/config/M221.cpp b/Marlin/src/gcode/config/M221.cpp
new file mode 100644
index 0000000..7ba22d1
--- /dev/null
+++ b/Marlin/src/gcode/config/M221.cpp
@@ -0,0 +1,47 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+
+#if EXTRUDERS
+
+/**
+ * M221: Set extrusion percentage (M221 T0 S95)
+ */
+void GcodeSuite::M221() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ if (parser.seenval('S'))
+ planner.set_flow(target_extruder, parser.value_int());
+ else {
+ SERIAL_ECHO_START();
+ SERIAL_CHAR('E', '0' + target_extruder);
+ SERIAL_ECHOPAIR(" Flow: ", planner.flow_percentage[target_extruder]);
+ SERIAL_CHAR('%');
+ SERIAL_EOL();
+ }
+}
+
+#endif // EXTRUDERS
diff --git a/Marlin/src/gcode/config/M281.cpp b/Marlin/src/gcode/config/M281.cpp
new file mode 100644
index 0000000..018ca1c
--- /dev/null
+++ b/Marlin/src/gcode/config/M281.cpp
@@ -0,0 +1,68 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(EDITABLE_SERVO_ANGLES)
+
+#include "../gcode.h"
+#include "../../module/servo.h"
+
+/**
+ * M281 - Edit / Report Servo Angles
+ *
+ * P<index> - Servo to update
+ * L<angle> - Deploy Angle
+ * U<angle> - Stowed Angle
+ */
+void GcodeSuite::M281() {
+ if (!parser.seenval('P')) return;
+ const int servo_index = parser.value_int();
+ if (WITHIN(servo_index, 0, NUM_SERVOS - 1)) {
+ #if ENABLED(BLTOUCH)
+ if (servo_index == Z_PROBE_SERVO_NR) {
+ SERIAL_ERROR_MSG("BLTouch angles can't be changed.");
+ return;
+ }
+ #endif
+ bool angle_change = false;
+ if (parser.seen('L')) {
+ servo_angles[servo_index][0] = parser.value_int();
+ angle_change = true;
+ }
+ if (parser.seen('U')) {
+ servo_angles[servo_index][1] = parser.value_int();
+ angle_change = true;
+ }
+ if (!angle_change) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(" Servo ", servo_index,
+ " L", servo_angles[servo_index][0],
+ " U", servo_angles[servo_index][1]);
+ }
+ }
+ else {
+ SERIAL_ERROR_START();
+ SERIAL_ECHOLNPAIR("Servo ", servo_index, " out of range");
+ }
+}
+
+#endif // EDITABLE_SERVO_ANGLES
diff --git a/Marlin/src/gcode/config/M301.cpp b/Marlin/src/gcode/config/M301.cpp
new file mode 100644
index 0000000..7b3f576
--- /dev/null
+++ b/Marlin/src/gcode/config/M301.cpp
@@ -0,0 +1,91 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(PIDTEMP)
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+
+/**
+ * M301: Set PID parameters P I D (and optionally C, L)
+ *
+ * E[extruder] Default: 0
+ *
+ * P[float] Kp term
+ * I[float] Ki term (unscaled)
+ * D[float] Kd term (unscaled)
+ *
+ * With PID_EXTRUSION_SCALING:
+ *
+ * C[float] Kc term
+ * L[int] LPQ length
+ *
+ * With PID_FAN_SCALING:
+ *
+ * F[float] Kf term
+ */
+void GcodeSuite::M301() {
+
+ // multi-extruder PID patch: M301 updates or prints a single extruder's PID values
+ // default behavior (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.seenval('L')) thermalManager.lpq_len = parser.value_int();
+ NOMORE(thermalManager.lpq_len, LPQ_MAX_LEN);
+ NOLESS(thermalManager.lpq_len, 0);
+ #endif
+
+ #if ENABLED(PID_FAN_SCALING)
+ if (parser.seen('F')) PID_PARAM(Kf, e) = parser.value_float();
+ #endif
+
+ thermalManager.updatePID();
+
+ SERIAL_ECHO_START();
+ #if ENABLED(PID_PARAMS_PER_HOTEND)
+ SERIAL_ECHOPAIR(" e:", e); // specify extruder in serial output
+ #endif
+ SERIAL_ECHOPAIR(" p:", PID_PARAM(Kp, e),
+ " i:", unscalePID_i(PID_PARAM(Ki, e)),
+ " d:", unscalePID_d(PID_PARAM(Kd, e)));
+ #if ENABLED(PID_EXTRUSION_SCALING)
+ SERIAL_ECHOPAIR(" c:", PID_PARAM(Kc, e));
+ #endif
+ #if ENABLED(PID_FAN_SCALING)
+ SERIAL_ECHOPAIR(" f:", PID_PARAM(Kf, e));
+ #endif
+
+ SERIAL_EOL();
+ }
+ else
+ SERIAL_ERROR_MSG(STR_INVALID_EXTRUDER);
+}
+
+#endif // PIDTEMP
diff --git a/Marlin/src/gcode/config/M302.cpp b/Marlin/src/gcode/config/M302.cpp
new file mode 100644
index 0000000..afdc6c9
--- /dev/null
+++ b/Marlin/src/gcode/config/M302.cpp
@@ -0,0 +1,63 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(PREVENT_COLD_EXTRUSION)
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+
+/**
+ * M302: Allow cold extrudes, or set the minimum extrude temperature
+ *
+ * S<temperature> sets the minimum extrude temperature
+ * P<bool> enables (1) or disables (0) cold extrusion
+ *
+ * Examples:
+ *
+ * M302 ; report current cold extrusion state
+ * M302 P0 ; enable cold extrusion checking
+ * M302 P1 ; disable 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
+ */
+void GcodeSuite::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_ECHOPGM("Cold extrudes are ");
+ serialprintPGM(thermalManager.allow_cold_extrude ? PSTR("en") : PSTR("dis"));
+ SERIAL_ECHOLNPAIR("abled (min temp ", thermalManager.extrude_min_temp, "C)");
+ }
+}
+
+#endif // PREVENT_COLD_EXTRUSION
diff --git a/Marlin/src/gcode/config/M304.cpp b/Marlin/src/gcode/config/M304.cpp
new file mode 100644
index 0000000..10739be
--- /dev/null
+++ b/Marlin/src/gcode/config/M304.cpp
@@ -0,0 +1,48 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(PIDTEMPBED)
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+
+/**
+ * M304 - Set and/or Report the current Bed PID values
+ *
+ * P<pval> - Set the P value
+ * I<ival> - Set the I value
+ * D<dval> - Set the D value
+ */
+void GcodeSuite::M304() {
+ if (parser.seen('P')) thermalManager.temp_bed.pid.Kp = parser.value_float();
+ if (parser.seen('I')) thermalManager.temp_bed.pid.Ki = scalePID_i(parser.value_float());
+ if (parser.seen('D')) thermalManager.temp_bed.pid.Kd = scalePID_d(parser.value_float());
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(" p:", thermalManager.temp_bed.pid.Kp,
+ " i:", unscalePID_i(thermalManager.temp_bed.pid.Ki),
+ " d:", unscalePID_d(thermalManager.temp_bed.pid.Kd));
+}
+
+#endif // PIDTEMPBED
diff --git a/Marlin/src/gcode/config/M305.cpp b/Marlin/src/gcode/config/M305.cpp
new file mode 100644
index 0000000..3e7206a
--- /dev/null
+++ b/Marlin/src/gcode/config/M305.cpp
@@ -0,0 +1,81 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_USER_THERMISTORS
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+
+/**
+ * M305: Set (or report) custom thermistor parameters
+ *
+ * P[index] Thermistor table index
+ * R[ohms] Pullup resistor value
+ * T[ohms] Resistance at 25C
+ * B[beta] Thermistor "beta" value
+ * C[coeff] Steinhart-Hart Coefficient 'C'
+ *
+ * Format: M305 P[tbl_index] R[pullup_resistor_val] T[therm_25C_resistance] B[therm_beta] C[Steinhart_Hart_C_coeff]
+ *
+ * Examples: M305 P0 R4700 T100000 B3950 C0.0
+ * M305 P0 R4700
+ * M305 P0 T100000
+ * M305 P0 B3950
+ * M305 P0 C0.0
+ */
+void GcodeSuite::M305() {
+ const int8_t t_index = parser.intval('P', -1);
+ const bool do_set = parser.seen("BCRT");
+
+ // A valid P index is required
+ if (t_index >= (USER_THERMISTORS) || (do_set && t_index < 0)) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR("!Invalid index. (0 <= P <= ", int(USER_THERMISTORS - 1), ")");
+ }
+ else if (do_set) {
+ if (parser.seen('R')) // Pullup resistor value
+ if (!thermalManager.set_pull_up_res(t_index, parser.value_float()))
+ SERIAL_ECHO_MSG("!Invalid series resistance. (0 < R < 1000000)");
+
+ if (parser.seen('T')) // Resistance at 25C
+ if (!thermalManager.set_res25(t_index, parser.value_float()))
+ SERIAL_ECHO_MSG("!Invalid 25C resistance. (0 < T < 10000000)");
+
+ if (parser.seen('B')) // Beta value
+ if (!thermalManager.set_beta(t_index, parser.value_float()))
+ SERIAL_ECHO_MSG("!Invalid beta. (0 < B < 1000000)");
+
+ if (parser.seen('C')) // Steinhart-Hart C coefficient
+ if (!thermalManager.set_sh_coeff(t_index, parser.value_float()))
+ SERIAL_ECHO_MSG("!Invalid Steinhart-Hart C coeff. (-0.01 < C < +0.01)");
+ } // If not setting then report parameters
+ else if (t_index < 0) { // ...all user thermistors
+ LOOP_L_N(i, USER_THERMISTORS)
+ thermalManager.log_user_thermistor(i);
+ }
+ else // ...one user thermistor
+ thermalManager.log_user_thermistor(t_index);
+}
+
+#endif // HAS_USER_THERMISTORS
diff --git a/Marlin/src/gcode/config/M43.cpp b/Marlin/src/gcode/config/M43.cpp
new file mode 100644
index 0000000..005fdf0
--- /dev/null
+++ b/Marlin/src/gcode/config/M43.cpp
@@ -0,0 +1,386 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(PINS_DEBUGGING)
+
+#include "../gcode.h"
+#include "../../MarlinCore.h" // for pin_is_protected
+#include "../../pins/pinsDebug.h"
+#include "../../module/endstops.h"
+
+#if HAS_Z_SERVO_PROBE
+ #include "../../module/probe.h"
+ #include "../../module/servo.h"
+#endif
+
+#if ENABLED(BLTOUCH)
+ #include "../../feature/bltouch.h"
+#endif
+
+#if ENABLED(HOST_PROMPT_SUPPORT)
+ #include "../../feature/host_actions.h"
+#endif
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../lcd/extui/ui_api.h"
+#endif
+
+#if HAS_RESUME_CONTINUE
+ #include "../../lcd/marlinui.h"
+#endif
+
+#ifndef GET_PIN_MAP_PIN_M43
+ #define GET_PIN_MAP_PIN_M43(Q) GET_PIN_MAP_PIN(Q)
+#endif
+
+inline void toggle_pins() {
+ const bool ignore_protection = parser.boolval('I');
+ const int repeat = parser.intval('R', 1),
+ start = PARSED_PIN_INDEX('S', 0),
+ end = PARSED_PIN_INDEX('L', NUM_DIGITAL_PINS - 1),
+ wait = parser.intval('W', 500);
+
+ LOOP_S_LE_N(i, start, end) {
+ pin_t pin = GET_PIN_MAP_PIN_M43(i);
+ if (!VALID_PIN(pin)) continue;
+ if (M43_NEVER_TOUCH(i) || (!ignore_protection && pin_is_protected(pin))) {
+ report_pin_state_extended(pin, ignore_protection, true, PSTR("Untouched "));
+ SERIAL_EOL();
+ }
+ else {
+ watchdog_refresh();
+ report_pin_state_extended(pin, ignore_protection, true, PSTR("Pulsing "));
+ #ifdef __STM32F1__
+ const auto prior_mode = _GET_MODE(i);
+ #else
+ const bool prior_mode = GET_PINMODE(pin);
+ #endif
+ #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++) {
+ watchdog_refresh(); extDigitalWrite(pin, 0); safe_delay(wait);
+ watchdog_refresh(); extDigitalWrite(pin, 1); safe_delay(wait);
+ watchdog_refresh(); extDigitalWrite(pin, 0); safe_delay(wait);
+ watchdog_refresh();
+ }
+ }
+ #ifdef __STM32F1__
+ _SET_MODE(i, prior_mode);
+ #else
+ pinMode(pin, prior_mode);
+ #endif
+ }
+ SERIAL_EOL();
+ }
+ SERIAL_ECHOLNPGM("Done.");
+
+} // toggle_pins
+
+inline void servo_probe_test() {
+
+ #if !(NUM_SERVOS > 0 && HAS_SERVO_0)
+
+ SERIAL_ERROR_MSG("SERVO not set up.");
+
+ #elif !HAS_Z_SERVO_PROBE
+
+ SERIAL_ERROR_MSG("Z_PROBE_SERVO_NR not set up.");
+
+ #else // HAS_Z_SERVO_PROBE
+
+ const uint8_t probe_index = parser.byteval('P', Z_PROBE_SERVO_NR);
+
+ SERIAL_ECHOLNPAIR("Servo probe test\n"
+ ". using index: ", int(probe_index),
+ ", deploy angle: ", servo_angles[probe_index][0],
+ ", stow angle: ", servo_angles[probe_index][1]
+ );
+
+ bool deploy_state = false, stow_state;
+
+ #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
+
+ #define PROBE_TEST_PIN Z_MIN_PIN
+ constexpr bool probe_inverting = Z_MIN_ENDSTOP_INVERTING;
+
+ SERIAL_ECHOLNPAIR(". Probe Z_MIN_PIN: ", int(PROBE_TEST_PIN));
+ SERIAL_ECHOPGM(". Z_MIN_ENDSTOP_INVERTING: ");
+
+ #else
+
+ #define PROBE_TEST_PIN Z_MIN_PROBE_PIN
+ constexpr bool probe_inverting = Z_MIN_PROBE_ENDSTOP_INVERTING;
+
+ SERIAL_ECHOLNPAIR(". Probe Z_MIN_PROBE_PIN: ", int(PROBE_TEST_PIN));
+ SERIAL_ECHOPGM( ". Z_MIN_PROBE_ENDSTOP_INVERTING: ");
+
+ #endif
+
+ serialprint_truefalse(probe_inverting);
+ SERIAL_EOL();
+
+ SET_INPUT_PULLUP(PROBE_TEST_PIN);
+
+ // First, check for a probe that recognizes an advanced BLTouch sequence.
+ // In addition to STOW and DEPLOY, it uses SW MODE (and RESET in the beginning)
+ // to see if this is one of the following: BLTOUCH Classic 1.2, 1.3, or
+ // BLTouch Smart 1.0, 2.0, 2.2, 3.0, 3.1. But only if the user has actually
+ // configured a BLTouch as being present. If the user has not configured this,
+ // the BLTouch will be detected in the last phase of these tests (see further on).
+ bool blt = false;
+ // This code will try to detect a BLTouch probe or clone
+ #if ENABLED(BLTOUCH)
+ SERIAL_ECHOLNPGM(". Check for BLTOUCH");
+ bltouch._reset();
+ bltouch._stow();
+ if (probe_inverting == READ(PROBE_TEST_PIN)) {
+ bltouch._set_SW_mode();
+ if (probe_inverting != READ(PROBE_TEST_PIN)) {
+ bltouch._deploy();
+ if (probe_inverting == READ(PROBE_TEST_PIN)) {
+ bltouch._stow();
+ SERIAL_ECHOLNPGM("= BLTouch Classic 1.2, 1.3, Smart 1.0, 2.0, 2.2, 3.0, 3.1 detected.");
+ // Check for a 3.1 by letting the user trigger it, later
+ blt = true;
+ }
+ }
+ }
+ #endif
+
+ // The following code is common to all kinds of servo probes.
+ // Since it could be a real servo or a BLTouch (any kind) or a clone,
+ // use only "common" functions - i.e. SERVO_MOVE. No bltouch.xxxx stuff.
+
+ // If it is already recognised as a being a BLTouch, no need for this test
+ if (!blt) {
+ // DEPLOY and STOW 4 times and see if the signal follows
+ // Then it is a mechanical switch
+ uint8_t i = 0;
+ SERIAL_ECHOLNPGM(". Deploy & stow 4 times");
+ do {
+ MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][0]); // Deploy
+ safe_delay(500);
+ deploy_state = READ(PROBE_TEST_PIN);
+ MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][1]); // Stow
+ safe_delay(500);
+ stow_state = READ(PROBE_TEST_PIN);
+ } while (++i < 4);
+
+ if (probe_inverting != deploy_state) SERIAL_ECHOLNPGM("WARNING: INVERTING setting probably backwards.");
+
+ if (deploy_state != stow_state) {
+ SERIAL_ECHOLNPGM("= Mechanical Switch detected");
+ if (deploy_state) {
+ SERIAL_ECHOLNPAIR(" DEPLOYED state: HIGH (logic 1)",
+ " STOWED (triggered) state: LOW (logic 0)");
+ }
+ else {
+ SERIAL_ECHOLNPAIR(" DEPLOYED state: LOW (logic 0)",
+ " STOWED (triggered) state: HIGH (logic 1)");
+ }
+ #if ENABLED(BLTOUCH)
+ SERIAL_ECHOLNPGM("FAIL: BLTOUCH enabled - Set up this device as a Servo Probe with INVERTING set to 'true'.");
+ #endif
+ return;
+ }
+ }
+
+ // Ask the user for a trigger event and measure the pulse width.
+ MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][0]); // Deploy
+ safe_delay(500);
+ SERIAL_ECHOLNPGM("** Please trigger probe within 30 sec **");
+ uint16_t probe_counter = 0;
+
+ // Wait 30 seconds for user to trigger probe
+ for (uint16_t j = 0; j < 500 * 30 && probe_counter == 0 ; j++) {
+ safe_delay(2);
+
+ if (0 == j % (500 * 1)) gcode.reset_stepper_timeout(); // Keep steppers powered
+
+ if (deploy_state != READ(PROBE_TEST_PIN)) { // probe triggered
+ for (probe_counter = 0; probe_counter < 15 && deploy_state != READ(PROBE_TEST_PIN); ++probe_counter) safe_delay(2);
+
+ SERIAL_ECHOPGM(". Pulse width");
+ if (probe_counter == 15)
+ SERIAL_ECHOLNPGM(": 30ms or more");
+ else
+ SERIAL_ECHOLNPAIR(" (+/- 4ms): ", probe_counter * 2);
+
+ if (probe_counter >= 4) {
+ if (probe_counter == 15) {
+ if (blt) SERIAL_ECHOPGM("= BLTouch V3.1");
+ else SERIAL_ECHOPGM("= Z Servo Probe");
+ }
+ else SERIAL_ECHOPGM("= BLTouch pre V3.1 (or compatible)");
+ SERIAL_ECHOLNPGM(" detected.");
+ }
+ else SERIAL_ECHOLNPGM("FAIL: Noise detected - please re-run test");
+
+ MOVE_SERVO(probe_index, servo_angles[Z_PROBE_SERVO_NR][1]); // Stow
+ return;
+ }
+ }
+
+ if (!probe_counter) SERIAL_ECHOLNPGM("FAIL: No trigger detected");
+
+ #endif // HAS_Z_SERVO_PROBE
+
+} // 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> 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> Pin to read or watch. If omitted, read/watch all pins.
+ * I Flag to ignore Marlin's pin protection.
+ *
+ * M43 E<bool> - Enable / disable background endstop monitoring
+ * - Machine continues to operate
+ * - Reports changes to endstops
+ * - Toggles LED_PIN when an endstop changes
+ * - Cannot reliably catch the 5mS pulse from BLTouch type probes
+ *
+ * M43 T - Toggle pin(s) and report which pin is being toggled
+ * S<pin> - Start Pin number. If not given, will default to 0
+ * L<pin> - End Pin number. If not given, will default to last pin defined for this board
+ * I<bool> - 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<index> - Probe index (optional - defaults to 0
+ */
+void GcodeSuite::M43() {
+
+ // 'T' must be first. It uses 'S' and 'E' differently.
+ if (parser.seen('T')) return toggle_pins();
+
+ // 'E' Enable or disable endstop monitoring and return
+ if (parser.seen('E')) {
+ endstops.monitor_flag = parser.value_bool();
+ SERIAL_ECHOPGM("endstop monitor ");
+ serialprintPGM(endstops.monitor_flag ? PSTR("en") : PSTR("dis"));
+ SERIAL_ECHOLNPGM("abled");
+ return;
+ }
+
+ // 'S' Run servo probe test and return
+ if (parser.seen('S')) return servo_probe_test();
+
+ // 'P' Get the range of pins to test or watch
+ uint8_t first_pin = PARSED_PIN_INDEX('P', 0),
+ last_pin = parser.seenval('P') ? first_pin : NUMBER_PINS_TOTAL - 1;
+
+ if (first_pin > last_pin) return;
+
+ // 'I' to ignore protected pins
+ const bool ignore_protection = parser.boolval('I');
+
+ // 'W' Watch until click, M108, or reset
+ if (parser.boolval('W')) {
+ SERIAL_ECHOLNPGM("Watching pins");
+ #ifdef ARDUINO_ARCH_SAM
+ NOLESS(first_pin, 2); // Don't hijack the UART pins
+ #endif
+ uint8_t pin_state[last_pin - first_pin + 1];
+ LOOP_S_LE_N(i, first_pin, last_pin) {
+ pin_t pin = GET_PIN_MAP_PIN_M43(i);
+ if (!VALID_PIN(pin)) continue;
+ if (M43_NEVER_TOUCH(i) || (!ignore_protection && pin_is_protected(pin))) continue;
+ pinMode(pin, INPUT_PULLUP);
+ delay(1);
+ /*
+ if (IS_ANALOG(pin))
+ pin_state[pin - first_pin] = analogRead(DIGITAL_PIN_TO_ANALOG_PIN(pin)); // int16_t pin_state[...]
+ else
+ //*/
+ pin_state[i - first_pin] = extDigitalRead(pin);
+ }
+
+ #if HAS_RESUME_CONTINUE
+ KEEPALIVE_STATE(PAUSED_FOR_USER);
+ wait_for_user = true;
+ TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, PSTR("M43 Wait Called"), CONTINUE_STR));
+ TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(PSTR("M43 Wait Called")));
+ #endif
+
+ for (;;) {
+ LOOP_S_LE_N(i, first_pin, last_pin) {
+ pin_t pin = GET_PIN_MAP_PIN_M43(i);
+ if (!VALID_PIN(pin)) continue;
+ if (M43_NEVER_TOUCH(i) || (!ignore_protection && pin_is_protected(pin))) continue;
+ const byte val =
+ /*
+ IS_ANALOG(pin)
+ ? analogRead(DIGITAL_PIN_TO_ANALOG_PIN(pin)) : // int16_t val
+ :
+ //*/
+ extDigitalRead(pin);
+ if (val != pin_state[i - first_pin]) {
+ report_pin_state_extended(pin, ignore_protection, false);
+ pin_state[i - first_pin] = val;
+ }
+ }
+
+ #if HAS_RESUME_CONTINUE
+ ui.update();
+ if (!wait_for_user) break;
+ #endif
+
+ safe_delay(200);
+ }
+ }
+ else {
+ // Report current state of selected pin(s)
+ LOOP_S_LE_N(i, first_pin, last_pin) {
+ pin_t pin = GET_PIN_MAP_PIN_M43(i);
+ if (VALID_PIN(pin)) report_pin_state_extended(pin, ignore_protection, true);
+ }
+ }
+}
+
+#endif // PINS_DEBUGGING
diff --git a/Marlin/src/gcode/config/M540.cpp b/Marlin/src/gcode/config/M540.cpp
new file mode 100644
index 0000000..54d52f3
--- /dev/null
+++ b/Marlin/src/gcode/config/M540.cpp
@@ -0,0 +1,40 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SD_ABORT_ON_ENDSTOP_HIT)
+
+#include "../gcode.h"
+#include "../../module/stepper.h"
+
+/**
+ * M540: Set whether SD card print should abort on endstop hit (M540 S<0|1>)
+ */
+void GcodeSuite::M540() {
+
+ if (parser.seen('S'))
+ planner.abort_on_endstop_hit = parser.value_bool();
+
+}
+
+#endif // SD_ABORT_ON_ENDSTOP_HIT
diff --git a/Marlin/src/gcode/config/M575.cpp b/Marlin/src/gcode/config/M575.cpp
new file mode 100644
index 0000000..44723b7
--- /dev/null
+++ b/Marlin/src/gcode/config/M575.cpp
@@ -0,0 +1,75 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(BAUD_RATE_GCODE)
+
+#include "../gcode.h"
+
+/**
+ * M575 - Change serial baud rate
+ *
+ * P<index> - Serial port index. Omit for all.
+ * B<baudrate> - Baud rate (bits per second)
+ */
+void GcodeSuite::M575() {
+ int32_t baud = parser.ulongval('B');
+ switch (baud) {
+ case 24:
+ case 96:
+ case 192:
+ case 384:
+ case 576:
+ case 1152: baud *= 100; break;
+ case 250:
+ case 500: baud *= 1000; break;
+ case 19: baud = 19200; break;
+ case 38: baud = 38400; break;
+ case 57: baud = 57600; break;
+ case 115: baud = 115200; break;
+ }
+ switch (baud) {
+ case 2400: case 9600: case 19200: case 38400: case 57600:
+ case 115200: case 250000: case 500000: case 1000000: {
+ const int8_t port = parser.intval('P', -99);
+ const bool set0 = (port == -99 || port == 0);
+ if (set0) SERIAL_ECHO_MSG(" Serial ", '0', " baud rate set to ", baud);
+ #if HAS_MULTI_SERIAL
+ const bool set1 = (port == -99 || port == 1);
+ if (set1) SERIAL_ECHO_MSG(" Serial ", '1', " baud rate set to ", baud);
+ #endif
+
+ SERIAL_FLUSH();
+
+ if (set0) { MYSERIAL0.end(); MYSERIAL0.begin(baud); }
+
+ #if HAS_MULTI_SERIAL
+ if (set1) { MYSERIAL1.end(); MYSERIAL1.begin(baud); }
+ #endif
+
+ } break;
+ default: SERIAL_ECHO_MSG("?(B)aud rate implausible.");
+ }
+}
+
+#endif // BAUD_RATE_GCODE
diff --git a/Marlin/src/gcode/config/M672.cpp b/Marlin/src/gcode/config/M672.cpp
new file mode 100644
index 0000000..af74230
--- /dev/null
+++ b/Marlin/src/gcode/config/M672.cpp
@@ -0,0 +1,98 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(DUET_SMART_EFFECTOR) && PIN_EXISTS(SMART_EFFECTOR_MOD)
+
+#include "../gcode.h"
+#include "../../HAL/shared/Delay.h"
+#include "../parser.h"
+
+/**
+ * The Marlin format for the M672 command is different than shown in the Duet Smart Effector
+ * documentation https://duet3d.dozuki.com/Wiki/Smart_effector_and_carriage_adapters_for_delta_printer
+ *
+ * To set custom sensitivity:
+ * Duet: M672 S105:aaa:bbb
+ * Marlin: M672 Saaa
+ *
+ * (where aaa is the desired sensitivity and bbb is 255 - aaa).
+ *
+ * Revert sensitivity to factory settings:
+ * Duet: M672 S105:131:131
+ * Marlin: M672 R
+ */
+
+#define M672_PROGBYTE 105 // magic byte to start programming custom sensitivity
+#define M672_ERASEBYTE 131 // magic byte to clear custom sensitivity
+
+//
+// Smart Effector byte send protocol:
+//
+// 0 0 1 0 ... always 0010
+// b7 b6 b5 b4 ~b4 ... hi bits, NOT last bit
+// b3 b2 b1 b0 ~b0 ... lo bits, NOT last bit
+//
+void M672_send(uint8_t b) { // bit rate requirement: 1KHz +/- 30%
+ LOOP_L_N(bits, 14) {
+ switch (bits) {
+ default: { OUT_WRITE(SMART_EFFECTOR_MOD_PIN, !!(b & 0x80)); b <<= 1; break; } // send bit, shift next into place
+ case 7:
+ case 12: { OUT_WRITE(SMART_EFFECTOR_MOD_PIN, !!(b & 0x80)); break; } // send bit. no shift
+ case 8:
+ case 13: { OUT_WRITE(SMART_EFFECTOR_MOD_PIN, !(b & 0x80)); b <<= 1; break; } // send inverted previous bit
+ case 0: case 1: // 00
+ case 3: { OUT_WRITE(SMART_EFFECTOR_MOD_PIN, LOW); break; } // 0010
+ case 2: { OUT_WRITE(SMART_EFFECTOR_MOD_PIN, HIGH); break; } // 001
+ }
+ DELAY_US(1000);
+ }
+}
+
+/**
+ * M672 - Set/reset Duet Smart Effector sensitivity
+ *
+ * One of these is required:
+ * S<sensitivity> - 0-255
+ * R - Flag to reset sensitivity to default
+ */
+void GcodeSuite::M672() {
+ if (parser.seen('R')) {
+ M672_send(M672_ERASEBYTE);
+ M672_send(M672_ERASEBYTE);
+ }
+ else if (parser.seenval('S')) {
+ const int8_t M672_sensitivity = parser.value_byte();
+ M672_send(M672_PROGBYTE);
+ M672_send(M672_sensitivity);
+ M672_send(255 - M672_sensitivity);
+ }
+ else {
+ SERIAL_ECHO_MSG("!'S' or 'R' parameter required.");
+ return;
+ }
+
+ OUT_WRITE(SMART_EFFECTOR_MOD_PIN, LOW); // Keep Smart Effector in NORMAL mode
+}
+
+#endif // DUET_SMART_EFFECTOR && SMART_EFFECTOR_MOD_PIN
diff --git a/Marlin/src/gcode/config/M92.cpp b/Marlin/src/gcode/config/M92.cpp
new file mode 100644
index 0000000..0a7d52b
--- /dev/null
+++ b/Marlin/src/gcode/config/M92.cpp
@@ -0,0 +1,114 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+
+void report_M92(const bool echo=true, const int8_t e=-1) {
+ if (echo) SERIAL_ECHO_START(); else SERIAL_CHAR(' ');
+ SERIAL_ECHOPAIR_P(PSTR(" M92 X"), LINEAR_UNIT(planner.settings.axis_steps_per_mm[X_AXIS]),
+ SP_Y_STR, LINEAR_UNIT(planner.settings.axis_steps_per_mm[Y_AXIS]),
+ SP_Z_STR, LINEAR_UNIT(planner.settings.axis_steps_per_mm[Z_AXIS]));
+ #if DISABLED(DISTINCT_E_FACTORS)
+ SERIAL_ECHOPAIR_P(SP_E_STR, VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS]));
+ #endif
+ SERIAL_EOL();
+
+ #if ENABLED(DISTINCT_E_FACTORS)
+ LOOP_L_N(i, E_STEPPERS) {
+ if (e >= 0 && i != e) continue;
+ if (echo) SERIAL_ECHO_START(); else SERIAL_CHAR(' ');
+ SERIAL_ECHOLNPAIR_P(PSTR(" M92 T"), (int)i,
+ SP_E_STR, VOLUMETRIC_UNIT(planner.settings.axis_steps_per_mm[E_AXIS_N(i)]));
+ }
+ #endif
+
+ UNUSED_E(e);
+}
+
+/**
+ * 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.
+ *
+ * If no argument is given print the current values.
+ *
+ * With MAGIC_NUMBERS_GCODE:
+ * Use 'H' and/or 'L' to get ideal layer-height information.
+ * 'H' specifies micro-steps to use. We guess if it's not supplied.
+ * 'L' specifies a desired layer height. Nearest good heights are shown.
+ */
+void GcodeSuite::M92() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ // No arguments? Show M92 report.
+ if (!parser.seen("XYZE"
+ #if ENABLED(MAGIC_NUMBERS_GCODE)
+ "HL"
+ #endif
+ )) return report_M92(true, target_extruder);
+
+ LOOP_XYZE(i) {
+ if (parser.seenval(axis_codes[i])) {
+ if (i == E_AXIS) {
+ const float value = parser.value_per_axis_units((AxisEnum)(E_AXIS_N(target_extruder)));
+ if (value < 20) {
+ float factor = planner.settings.axis_steps_per_mm[E_AXIS_N(target_extruder)] / value; // increase e constants if M92 E14 is given for netfab.
+ #if HAS_CLASSIC_JERK && HAS_CLASSIC_E_JERK
+ planner.max_jerk.e *= factor;
+ #endif
+ planner.settings.max_feedrate_mm_s[E_AXIS_N(target_extruder)] *= factor;
+ planner.max_acceleration_steps_per_s2[E_AXIS_N(target_extruder)] *= factor;
+ }
+ planner.settings.axis_steps_per_mm[E_AXIS_N(target_extruder)] = value;
+ }
+ else {
+ planner.settings.axis_steps_per_mm[i] = parser.value_per_axis_units((AxisEnum)i);
+ }
+ }
+ }
+ planner.refresh_positioning();
+
+ #if ENABLED(MAGIC_NUMBERS_GCODE)
+ #ifndef Z_MICROSTEPS
+ #define Z_MICROSTEPS 16
+ #endif
+ const float wanted = parser.floatval('L');
+ if (parser.seen('H') || wanted) {
+ const uint16_t argH = parser.ushortval('H'),
+ micro_steps = argH ?: Z_MICROSTEPS;
+ const float z_full_step_mm = micro_steps * planner.steps_to_mm[Z_AXIS];
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPAIR("{ micro_steps:", micro_steps, ", z_full_step_mm:", z_full_step_mm);
+ if (wanted) {
+ const float best = uint16_t(wanted / z_full_step_mm) * z_full_step_mm;
+ SERIAL_ECHOPAIR(", best:[", best);
+ if (best != wanted) { SERIAL_CHAR(','); SERIAL_DECIMAL(best + z_full_step_mm); }
+ SERIAL_CHAR(']');
+ }
+ SERIAL_ECHOLNPGM(" }");
+ }
+ #endif
+}
diff --git a/Marlin/src/gcode/control/M108_M112_M410.cpp b/Marlin/src/gcode/control/M108_M112_M410.cpp
new file mode 100644
index 0000000..309c806
--- /dev/null
+++ b/Marlin/src/gcode/control/M108_M112_M410.cpp
@@ -0,0 +1,56 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if DISABLED(EMERGENCY_PARSER)
+
+#include "../gcode.h"
+#include "../../MarlinCore.h" // for wait_for_heatup, kill, M112_KILL_STR
+#include "../../module/motion.h" // for quickstop_stepper
+
+/**
+ * M108: Stop the waiting for heaters in M109, M190, M303. Does not affect the target temperature.
+ */
+void GcodeSuite::M108() {
+ TERN_(HAS_RESUME_CONTINUE, wait_for_user = false);
+ wait_for_heatup = false;
+}
+
+/**
+ * M112: Full Shutdown
+ */
+void GcodeSuite::M112() {
+ kill(M112_KILL_STR, nullptr, true);
+}
+
+/**
+ * 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.
+ */
+void GcodeSuite::M410() {
+ quickstop_stepper();
+}
+
+#endif // !EMERGENCY_PARSER
diff --git a/Marlin/src/gcode/control/M111.cpp b/Marlin/src/gcode/control/M111.cpp
new file mode 100644
index 0000000..38cb065
--- /dev/null
+++ b/Marlin/src/gcode/control/M111.cpp
@@ -0,0 +1,77 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+
+/**
+ * M111: Set the debug level
+ */
+void GcodeSuite::M111() {
+ if (parser.seen('S')) marlin_debug_flags = parser.byteval('S');
+
+ static PGMSTR(str_debug_1, STR_DEBUG_ECHO);
+ static PGMSTR(str_debug_2, STR_DEBUG_INFO);
+ static PGMSTR(str_debug_4, STR_DEBUG_ERRORS);
+ static PGMSTR(str_debug_8, STR_DEBUG_DRYRUN);
+ static PGMSTR(str_debug_16, STR_DEBUG_COMMUNICATION);
+ #if ENABLED(DEBUG_LEVELING_FEATURE)
+ static PGMSTR(str_debug_lvl, STR_DEBUG_LEVELING);
+ #endif
+
+ static PGM_P const debug_strings[] PROGMEM = {
+ str_debug_1, str_debug_2, str_debug_4, str_debug_8, str_debug_16,
+ TERN_(DEBUG_LEVELING_FEATURE, str_debug_lvl)
+ };
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(STR_DEBUG_PREFIX);
+ if (marlin_debug_flags) {
+ uint8_t comma = 0;
+ LOOP_L_N(i, COUNT(debug_strings)) {
+ if (TEST(marlin_debug_flags, i)) {
+ if (comma++) SERIAL_CHAR(',');
+ serialprintPGM((char*)pgm_read_ptr(&debug_strings[i]));
+ }
+ }
+ }
+ else {
+ SERIAL_ECHOPGM(STR_DEBUG_OFF);
+ #if !defined(__AVR__) || !defined(USBCON)
+ #if ENABLED(SERIAL_STATS_RX_BUFFER_OVERRUNS)
+ SERIAL_ECHOPAIR("\nBuffer Overruns: ", MYSERIAL0.buffer_overruns());
+ #endif
+
+ #if ENABLED(SERIAL_STATS_RX_FRAMING_ERRORS)
+ SERIAL_ECHOPAIR("\nFraming Errors: ", MYSERIAL0.framing_errors());
+ #endif
+
+ #if ENABLED(SERIAL_STATS_DROPPED_RX)
+ SERIAL_ECHOPAIR("\nDropped bytes: ", MYSERIAL0.dropped());
+ #endif
+
+ #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
+ SERIAL_ECHOPAIR("\nMax RX Queue Size: ", MYSERIAL0.rxMaxEnqueued());
+ #endif
+ #endif // !__AVR__ || !USBCON
+ }
+ SERIAL_EOL();
+}
diff --git a/Marlin/src/gcode/control/M120_M121.cpp b/Marlin/src/gcode/control/M120_M121.cpp
new file mode 100644
index 0000000..570f2e9
--- /dev/null
+++ b/Marlin/src/gcode/control/M120_M121.cpp
@@ -0,0 +1,34 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/endstops.h"
+
+/**
+ * M120: Enable endstops and set non-homing endstop state to "enabled"
+ */
+void GcodeSuite::M120() { endstops.enable_globally(true); }
+
+/**
+ * M121: Disable endstops and set non-homing endstop state to "disabled"
+ */
+void GcodeSuite::M121() { endstops.enable_globally(false); }
diff --git a/Marlin/src/gcode/control/M17_M18_M84.cpp b/Marlin/src/gcode/control/M17_M18_M84.cpp
new file mode 100644
index 0000000..b35b465
--- /dev/null
+++ b/Marlin/src/gcode/control/M17_M18_M84.cpp
@@ -0,0 +1,69 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../MarlinCore.h" // for stepper_inactive_time, disable_e_steppers
+#include "../../lcd/marlinui.h"
+#include "../../module/stepper.h"
+
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+/**
+ * M17: Enable stepper motors
+ */
+void GcodeSuite::M17() {
+ if (parser.seen("XYZE")) {
+ if (parser.seen('X')) ENABLE_AXIS_X();
+ if (parser.seen('Y')) ENABLE_AXIS_Y();
+ if (parser.seen('Z')) ENABLE_AXIS_Z();
+ if (TERN0(HAS_E_STEPPER_ENABLE, parser.seen('E'))) enable_e_steppers();
+ }
+ else {
+ LCD_MESSAGEPGM(MSG_NO_MOVE);
+ enable_all_steppers();
+ }
+}
+
+/**
+ * M18, M84: Disable stepper motors
+ */
+void GcodeSuite::M18_M84() {
+ if (parser.seenval('S')) {
+ reset_stepper_timeout();
+ stepper_inactive_time = parser.value_millis_from_seconds();
+ }
+ else {
+ if (parser.seen("XYZE")) {
+ planner.synchronize();
+ if (parser.seen('X')) DISABLE_AXIS_X();
+ if (parser.seen('Y')) DISABLE_AXIS_Y();
+ if (parser.seen('Z')) DISABLE_AXIS_Z();
+ if (TERN0(HAS_E_STEPPER_ENABLE, parser.seen('E'))) disable_e_steppers();
+ }
+ else
+ planner.finish_and_disable();
+
+ TERN_(AUTO_BED_LEVELING_UBL, ubl.steppers_were_disabled());
+ }
+}
diff --git a/Marlin/src/gcode/control/M211.cpp b/Marlin/src/gcode/control/M211.cpp
new file mode 100644
index 0000000..2049f1e
--- /dev/null
+++ b/Marlin/src/gcode/control/M211.cpp
@@ -0,0 +1,46 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_SOFTWARE_ENDSTOPS
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+
+/**
+ * M211: Enable, Disable, and/or Report software endstops
+ *
+ * Usage: M211 S1 to enable, M211 S0 to disable, M211 alone for report
+ */
+void GcodeSuite::M211() {
+ const xyz_pos_t l_soft_min = soft_endstop.min.asLogical(),
+ l_soft_max = soft_endstop.max.asLogical();
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(STR_SOFT_ENDSTOPS);
+ if (parser.seen('S')) soft_endstop._enabled = parser.value_bool();
+ serialprint_onoff(soft_endstop._enabled);
+ print_xyz(l_soft_min, PSTR(STR_SOFT_MIN), PSTR(" "));
+ print_xyz(l_soft_max, PSTR(STR_SOFT_MAX));
+}
+
+#endif
diff --git a/Marlin/src/gcode/control/M226.cpp b/Marlin/src/gcode/control/M226.cpp
new file mode 100644
index 0000000..63f022e
--- /dev/null
+++ b/Marlin/src/gcode/control/M226.cpp
@@ -0,0 +1,60 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(DIRECT_PIN_CONTROL)
+
+#include "../gcode.h"
+#include "../../MarlinCore.h" // for pin_is_protected and idle()
+#include "../../module/stepper.h"
+
+void protected_pin_err();
+
+/**
+ * M226: Wait until the specified pin reaches the state required (M226 P<pin> S<state>)
+ */
+void GcodeSuite::M226() {
+ if (parser.seen('P')) {
+ const int pin_number = PARSED_PIN_INDEX('P', 0),
+ pin_state = parser.intval('S', -1); // required pin state - default is inverted
+ const pin_t pin = GET_PIN_MAP_PIN(pin_number);
+
+ if (WITHIN(pin_state, -1, 1) && pin > -1) {
+ if (pin_is_protected(pin))
+ protected_pin_err();
+ else {
+ int target = LOW;
+ planner.synchronize();
+ pinMode(pin, INPUT);
+ switch (pin_state) {
+ case 1: target = HIGH; break;
+ case 0: target = LOW; break;
+ case -1: target = !extDigitalRead(pin); break;
+ }
+ while (int(extDigitalRead(pin)) != target) idle();
+ }
+ } // pin_state -1 0 1 && pin > -1
+ } // parser.seen('P')
+}
+
+#endif // DIRECT_PIN_CONTROL
diff --git a/Marlin/src/gcode/control/M280.cpp b/Marlin/src/gcode/control/M280.cpp
new file mode 100644
index 0000000..6ccbb7b
--- /dev/null
+++ b/Marlin/src/gcode/control/M280.cpp
@@ -0,0 +1,55 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_SERVOS
+
+#include "../gcode.h"
+#include "../../module/servo.h"
+
+/**
+ * M280: Get or set servo position. P<index> [S<angle>]
+ */
+void GcodeSuite::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')) {
+ const int a = parser.value_int();
+ if (a == -1)
+ servo[servo_index].detach();
+ else
+ MOVE_SERVO(servo_index, a);
+ }
+ else {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(" Servo ", servo_index, ": ", servo[servo_index].read());
+ }
+ }
+ else {
+ SERIAL_ERROR_START();
+ SERIAL_ECHOLNPAIR("Servo ", servo_index, " out of range");
+ }
+}
+
+#endif // HAS_SERVOS
diff --git a/Marlin/src/gcode/control/M3-M5.cpp b/Marlin/src/gcode/control/M3-M5.cpp
new file mode 100644
index 0000000..711bb7e
--- /dev/null
+++ b/Marlin/src/gcode/control/M3-M5.cpp
@@ -0,0 +1,140 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_CUTTER
+
+#include "../gcode.h"
+#include "../../feature/spindle_laser.h"
+#include "../../module/stepper.h"
+
+/**
+ * Laser:
+ * M3 - Laser ON/Power (Ramped power)
+ * M4 - Laser ON/Power (Continuous power)
+ *
+ * Spindle:
+ * M3 - Spindle ON (Clockwise)
+ * M4 - Spindle ON (Counter-clockwise)
+ *
+ * Parameters:
+ * S<power> - Set power. S0 will turn the spindle/laser off, except in relative mode.
+ * O<ocr> - Set power and OCR (oscillator count register)
+ *
+ * If no PWM pin is defined then M3/M4 just turns it on.
+ *
+ * At least 12.8KHz (50Hz * 256) is needed for Spindle PWM.
+ * Hardware PWM is required on AVR. 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_ENA_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).
+ */
+void GcodeSuite::M3_M4(const bool is_M4) {
+ auto get_s_power = [] {
+ if (parser.seenval('S')) {
+ const float spwr = parser.value_float();
+ #if ENABLED(SPINDLE_SERVO)
+ cutter.unitPower = spwr;
+ #else
+ cutter.unitPower = TERN(SPINDLE_LASER_PWM,
+ cutter.power_to_range(cutter_power_t(round(spwr))),
+ spwr > 0 ? 255 : 0);
+ #endif
+ }
+ else
+ cutter.unitPower = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP);
+ return cutter.unitPower;
+ };
+
+ #if ENABLED(LASER_POWER_INLINE)
+ if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
+ // Laser power in inline mode
+ cutter.inline_direction(is_M4); // Should always be unused
+ #if ENABLED(SPINDLE_LASER_PWM)
+ if (parser.seen('O')) {
+ cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0);
+ cutter.inline_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t)
+ }
+ else
+ cutter.inline_power(cutter.upower_to_ocr(get_s_power()));
+ #else
+ cutter.set_inline_enabled(true);
+ #endif
+ return;
+ }
+ // Non-inline, standard case
+ cutter.inline_disable(); // Prevent future blocks re-setting the power
+ #endif
+
+ planner.synchronize(); // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power
+ cutter.set_reverse(is_M4);
+
+ #if ENABLED(SPINDLE_LASER_PWM)
+ if (parser.seenval('O')) {
+ cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0);
+ cutter.set_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t)
+ }
+ else
+ cutter.set_power(cutter.upower_to_ocr(get_s_power()));
+ #elif ENABLED(SPINDLE_SERVO)
+ cutter.set_power(get_s_power());
+ #else
+ cutter.set_enabled(true);
+ #endif
+ cutter.menuPower = cutter.unitPower;
+}
+
+/**
+ * M5 - Cutter OFF (when moves are complete)
+ */
+void GcodeSuite::M5() {
+ #if ENABLED(LASER_POWER_INLINE)
+ if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
+ cutter.set_inline_enabled(false); // Laser power in inline mode
+ return;
+ }
+ // Non-inline, standard case
+ cutter.inline_disable(); // Prevent future blocks re-setting the power
+ #endif
+ planner.synchronize();
+ cutter.set_enabled(false);
+ cutter.menuPower = cutter.unitPower;
+}
+
+#endif // HAS_CUTTER
diff --git a/Marlin/src/gcode/control/M350_M351.cpp b/Marlin/src/gcode/control/M350_M351.cpp
new file mode 100644
index 0000000..463bd2a
--- /dev/null
+++ b/Marlin/src/gcode/control/M350_M351.cpp
@@ -0,0 +1,64 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_MICROSTEPS
+
+#include "../gcode.h"
+#include "../../module/stepper.h"
+
+/**
+ * M350: Set axis microstepping modes. S sets mode for all drivers.
+ *
+ * Warning: Steps-per-unit remains unchanged.
+ */
+void GcodeSuite::M350() {
+ if (parser.seen('S')) LOOP_LE_N(i, 4) 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, MS2 or MS3, X# sets the pin high/low.
+ */
+void GcodeSuite::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, -1);
+ if (parser.seenval('B')) stepper.microstep_ms(4, parser.value_byte(), -1, -1);
+ break;
+ case 2:
+ LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, -1, parser.value_byte(), -1);
+ if (parser.seenval('B')) stepper.microstep_ms(4, -1, parser.value_byte(), -1);
+ break;
+ case 3:
+ LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, -1, -1, parser.value_byte());
+ if (parser.seenval('B')) stepper.microstep_ms(4, -1, -1, parser.value_byte());
+ break;
+ }
+ stepper.microstep_readings();
+}
+
+#endif // HAS_MICROSTEPS
diff --git a/Marlin/src/gcode/control/M380_M381.cpp b/Marlin/src/gcode/control/M380_M381.cpp
new file mode 100644
index 0000000..3f5b252
--- /dev/null
+++ b/Marlin/src/gcode/control/M380_M381.cpp
@@ -0,0 +1,56 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if EITHER(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL)
+
+#include "../gcode.h"
+#include "../../feature/solenoid.h"
+#include "../../module/motion.h"
+
+/**
+ * M380: Enable solenoid on the active extruder
+ *
+ * S<index> to specify a solenoid (Requires MANUAL_SOLENOID_CONTROL)
+ */
+void GcodeSuite::M380() {
+ #if ENABLED(MANUAL_SOLENOID_CONTROL)
+ enable_solenoid(parser.intval('S', active_extruder));
+ #else
+ enable_solenoid_on_active_extruder();
+ #endif
+}
+
+/**
+ * M381: Disable all solenoids if EXT_SOLENOID
+ * Disable selected/active solenoid if MANUAL_SOLENOID_CONTROL
+ */
+void GcodeSuite::M381() {
+ #if ENABLED(MANUAL_SOLENOID_CONTROL)
+ disable_solenoid(parser.intval('S', active_extruder));
+ #else
+ disable_all_solenoids();
+ #endif
+}
+
+#endif // EXT_SOLENOID || MANUAL_SOLENOID_CONTROL
diff --git a/Marlin/src/gcode/control/M400.cpp b/Marlin/src/gcode/control/M400.cpp
new file mode 100644
index 0000000..9a5ad4e
--- /dev/null
+++ b/Marlin/src/gcode/control/M400.cpp
@@ -0,0 +1,33 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/stepper.h"
+
+/**
+ * M400: Finish all moves
+ */
+void GcodeSuite::M400() {
+
+ planner.synchronize();
+
+}
diff --git a/Marlin/src/gcode/control/M42.cpp b/Marlin/src/gcode/control/M42.cpp
new file mode 100644
index 0000000..6ef8455
--- /dev/null
+++ b/Marlin/src/gcode/control/M42.cpp
@@ -0,0 +1,107 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(DIRECT_PIN_CONTROL)
+
+#include "../gcode.h"
+#include "../../MarlinCore.h" // for pin_is_protected
+
+#if HAS_FAN
+ #include "../../module/temperature.h"
+#endif
+
+void protected_pin_err() {
+ SERIAL_ERROR_MSG(STR_ERR_PROTECTED_PIN);
+}
+
+/**
+ * M42: Change pin status via GCode
+ *
+ * P<pin> Pin number (LED if omitted)
+ * For LPC1768 specify pin P1_02 as M42 P102,
+ * P1_20 as M42 P120, etc.
+ *
+ * S<byte> Pin status from 0 - 255
+ * I Flag to ignore Marlin's pin protection
+ *
+ * M<mode> Pin mode: 0=INPUT 1=OUTPUT 2=INPUT_PULLUP 3=INPUT_PULLDOWN
+ */
+void GcodeSuite::M42() {
+ const int pin_index = PARSED_PIN_INDEX('P', GET_PIN_MAP_INDEX(LED_PIN));
+ if (pin_index < 0) return;
+
+ const pin_t pin = GET_PIN_MAP_PIN(pin_index);
+
+ if (!parser.boolval('I') && pin_is_protected(pin)) return protected_pin_err();
+
+ if (parser.seenval('M')) {
+ switch (parser.value_byte()) {
+ case 0: pinMode(pin, INPUT); break;
+ case 1: pinMode(pin, OUTPUT); break;
+ case 2: pinMode(pin, INPUT_PULLUP); break;
+ #ifdef INPUT_PULLDOWN
+ case 3: pinMode(pin, INPUT_PULLDOWN); break;
+ #endif
+ default: SERIAL_ECHOLNPGM("Invalid Pin Mode"); return;
+ }
+ }
+
+ if (!parser.seenval('S')) return;
+ const byte pin_status = parser.value_byte();
+
+ #if HAS_FAN
+ switch (pin) {
+ #if HAS_FAN0
+ case FAN0_PIN: thermalManager.fan_speed[0] = pin_status; return;
+ #endif
+ #if HAS_FAN1
+ case FAN1_PIN: thermalManager.fan_speed[1] = pin_status; return;
+ #endif
+ #if HAS_FAN2
+ case FAN2_PIN: thermalManager.fan_speed[2] = pin_status; return;
+ #endif
+ #if HAS_FAN3
+ case FAN3_PIN: thermalManager.fan_speed[3] = pin_status; return;
+ #endif
+ #if HAS_FAN4
+ case FAN4_PIN: thermalManager.fan_speed[4] = pin_status; return;
+ #endif
+ #if HAS_FAN5
+ case FAN5_PIN: thermalManager.fan_speed[5] = pin_status; return;
+ #endif
+ #if HAS_FAN6
+ case FAN6_PIN: thermalManager.fan_speed[6] = pin_status; return;
+ #endif
+ #if HAS_FAN7
+ case FAN7_PIN: thermalManager.fan_speed[7] = pin_status; return;
+ #endif
+ }
+ #endif
+
+ pinMode(pin, OUTPUT);
+ extDigitalWrite(pin, pin_status);
+ analogWrite(pin, pin_status);
+}
+
+#endif // DIRECT_PIN_CONTROL
diff --git a/Marlin/src/gcode/control/M605.cpp b/Marlin/src/gcode/control/M605.cpp
new file mode 100644
index 0000000..0d7a9f4
--- /dev/null
+++ b/Marlin/src/gcode/control/M605.cpp
@@ -0,0 +1,185 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_DUPLICATION_MODE
+
+//#define DEBUG_DXC_MODE
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/stepper.h"
+#include "../../module/tool_change.h"
+#include "../../module/planner.h"
+
+#define DEBUG_OUT ENABLED(DEBUG_DXC_MODE)
+#include "../../core/debug_out.h"
+
+#if ENABLED(DUAL_X_CARRIAGE)
+
+ /**
+ * M605: Set dual x-carriage movement mode
+ *
+ * M605 S0 : (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 S1 : (AUTO_PARK) The firmware automatically parks and unparks the X-carriages on tool-change so that
+ * additional slicer support is not required.
+ *
+ * M605 S2 X R : (DUPLICATION) The firmware moves the second X-carriage and extruder in synchronization with
+ * the first X-carriage and extruder, to print 2 copies of the same object at the same time.
+ * Set the constant X-offset and temperature differential with M605 S2 X[offs] R[deg] and
+ * follow with "M605 S2" to initiate duplicated movement. For example, use "M605 S2 X100 R2" to
+ * make a copy 100mm to the right with E1 2° hotter than E0.
+ *
+ * M605 S3 : (MIRRORED) Formbot/Vivedino-inspired mirrored mode in which the second extruder duplicates
+ * the movement of the first except the second extruder is reversed in the X axis.
+ * The temperature differential and initial X offset must be set with "M605 S2 X[offs] R[deg]",
+ * then followed by "M605 S3" to initiate mirrored movement.
+ *
+ * M605 W : IDEX What? command.
+ *
+ * Note: the X axis should be homed after changing Dual X-carriage mode.
+ */
+ void GcodeSuite::M605() {
+ planner.synchronize();
+
+ if (parser.seen('S')) {
+ const DualXMode previous_mode = dual_x_carriage_mode;
+
+ dual_x_carriage_mode = (DualXMode)parser.value_byte();
+ idex_set_mirrored_mode(false);
+
+ if (dual_x_carriage_mode == DXC_MIRRORED_MODE) {
+ if (previous_mode != DXC_DUPLICATION_MODE) {
+ SERIAL_ECHOLNPGM("Printer must be in DXC_DUPLICATION_MODE prior to ");
+ SERIAL_ECHOLNPGM("specifying DXC_MIRRORED_MODE.");
+ dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE;
+ return;
+ }
+ idex_set_mirrored_mode(true);
+ float x_jog = current_position.x - .1;
+ for (uint8_t i = 2; --i;) {
+ planner.buffer_line(x_jog, current_position.y, current_position.z, current_position.e, feedrate_mm_s, 0);
+ x_jog += .1;
+ }
+ return;
+ }
+
+ switch (dual_x_carriage_mode) {
+ case DXC_FULL_CONTROL_MODE:
+ case DXC_AUTO_PARK_MODE:
+ break;
+ case DXC_DUPLICATION_MODE:
+ // Set the X offset, but no less than the safety gap
+ if (parser.seen('X')) duplicate_extruder_x_offset = _MAX(parser.value_linear_units(), (X2_MIN_POS) - (X1_MIN_POS));
+ if (parser.seen('R')) duplicate_extruder_temp_offset = parser.value_celsius_diff();
+ // Always switch back to tool 0
+ if (active_extruder != 0) tool_change(0);
+ break;
+ default:
+ dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE;
+ break;
+ }
+ idex_set_parked(false);
+ set_duplication_enabled(false);
+
+ #ifdef EVENT_GCODE_IDEX_AFTER_MODECHANGE
+ gcode.process_subcommands_now_P(PSTR(EVENT_GCODE_IDEX_AFTER_MODECHANGE));
+ #endif
+ }
+ else if (!parser.seen('W')) // if no S or W parameter, the DXC mode gets reset to the user's default
+ dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE;
+
+ #ifdef DEBUG_DXC_MODE
+
+ if (parser.seen('W')) {
+ DEBUG_ECHO_START();
+ DEBUG_ECHOPGM("Dual X Carriage Mode ");
+ switch (dual_x_carriage_mode) {
+ case DXC_FULL_CONTROL_MODE: DEBUG_ECHOPGM("FULL_CONTROL"); break;
+ case DXC_AUTO_PARK_MODE: DEBUG_ECHOPGM("AUTO_PARK"); break;
+ case DXC_DUPLICATION_MODE: DEBUG_ECHOPGM("DUPLICATION"); break;
+ case DXC_MIRRORED_MODE: DEBUG_ECHOPGM("MIRRORED"); break;
+ }
+ DEBUG_ECHOPAIR("\nActive Ext: ", int(active_extruder));
+ if (!active_extruder_parked) DEBUG_ECHOPGM(" NOT ");
+ DEBUG_ECHOPGM(" parked.");
+ DEBUG_ECHOPAIR("\nactive_extruder_x_pos: ", current_position.x);
+ DEBUG_ECHOPAIR("\ninactive_extruder_x: ", inactive_extruder_x);
+ DEBUG_ECHOPAIR("\nextruder_duplication_enabled: ", int(extruder_duplication_enabled));
+ DEBUG_ECHOPAIR("\nduplicate_extruder_x_offset: ", duplicate_extruder_x_offset);
+ DEBUG_ECHOPAIR("\nduplicate_extruder_temp_offset: ", duplicate_extruder_temp_offset);
+ DEBUG_ECHOPAIR("\ndelayed_move_time: ", delayed_move_time);
+ DEBUG_ECHOPAIR("\nX1 Home X: ", x_home_pos(0), "\nX1_MIN_POS=", int(X1_MIN_POS), "\nX1_MAX_POS=", int(X1_MAX_POS));
+ DEBUG_ECHOPAIR("\nX2 Home X: ", x_home_pos(1), "\nX2_MIN_POS=", int(X2_MIN_POS), "\nX2_MAX_POS=", int(X2_MAX_POS));
+ DEBUG_ECHOPAIR("\nX2_HOME_DIR=", int(X2_HOME_DIR), "\nX2_HOME_POS=", int(X2_HOME_POS));
+ DEBUG_ECHOPAIR("\nDEFAULT_DUAL_X_CARRIAGE_MODE=", STRINGIFY(DEFAULT_DUAL_X_CARRIAGE_MODE));
+ DEBUG_ECHOPAIR("\toolchange_settings.z_raise=", toolchange_settings.z_raise);
+ DEBUG_ECHOPAIR("\nDEFAULT_DUPLICATION_X_OFFSET=", int(DEFAULT_DUPLICATION_X_OFFSET));
+ DEBUG_EOL();
+
+ HOTEND_LOOP() {
+ DEBUG_ECHOPAIR_P(SP_T_STR, int(e));
+ LOOP_XYZ(a) DEBUG_ECHOPAIR(" hotend_offset[", int(e), "].", XYZ_CHAR(a) | 0x20, "=", hotend_offset[e][a]);
+ DEBUG_EOL();
+ }
+ DEBUG_EOL();
+ }
+ #endif // DEBUG_DXC_MODE
+ }
+
+#elif ENABLED(MULTI_NOZZLE_DUPLICATION)
+
+ /**
+ * M605: Set multi-nozzle duplication mode
+ *
+ * S2 - Enable duplication mode
+ * P[mask] - Bit-mask of nozzles to include in the duplication set.
+ * A value of 0 disables duplication.
+ * E[index] - Last nozzle index to include in the duplication set.
+ * A value of 0 disables duplication.
+ */
+ void GcodeSuite::M605() {
+ bool ena = false;
+ if (parser.seen("EPS")) {
+ planner.synchronize();
+ if (parser.seenval('P')) duplication_e_mask = parser.value_int(); // Set the mask directly
+ else if (parser.seenval('E')) duplication_e_mask = pow(2, parser.value_int() + 1) - 1; // Set the mask by E index
+ ena = (2 == parser.intval('S', extruder_duplication_enabled ? 2 : 0));
+ set_duplication_enabled(ena && (duplication_e_mask >= 3));
+ }
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM(STR_DUPLICATION_MODE);
+ serialprint_onoff(extruder_duplication_enabled);
+ if (ena) {
+ SERIAL_ECHOPGM(" ( ");
+ HOTEND_LOOP() if (TEST(duplication_e_mask, e)) { SERIAL_ECHO(e); SERIAL_CHAR(' '); }
+ SERIAL_CHAR(')');
+ }
+ SERIAL_EOL();
+ }
+
+#endif // MULTI_NOZZLE_DUPLICATION
+
+#endif // HAS_DUPICATION_MODE
diff --git a/Marlin/src/gcode/control/M7-M9.cpp b/Marlin/src/gcode/control/M7-M9.cpp
new file mode 100644
index 0000000..a33e432
--- /dev/null
+++ b/Marlin/src/gcode/control/M7-M9.cpp
@@ -0,0 +1,63 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(COOLANT_CONTROL)
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+
+#if ENABLED(COOLANT_MIST)
+ /**
+ * M7: Mist Coolant On
+ */
+ void GcodeSuite::M7() {
+ planner.synchronize(); // Wait for move to arrive
+ WRITE(COOLANT_MIST_PIN, !(COOLANT_MIST_INVERT)); // Turn on Mist coolant
+ }
+#endif
+
+#if ENABLED(COOLANT_FLOOD)
+ /**
+ * M8: Flood Coolant On
+ */
+ void GcodeSuite::M8() {
+ planner.synchronize(); // Wait for move to arrive
+ WRITE(COOLANT_FLOOD_PIN, !(COOLANT_FLOOD_INVERT)); // Turn on Flood coolant
+ }
+#endif
+
+/**
+ * M9: Coolant OFF
+ */
+void GcodeSuite::M9() {
+ planner.synchronize(); // Wait for move to arrive
+ #if ENABLED(COOLANT_MIST)
+ WRITE(COOLANT_MIST_PIN, COOLANT_MIST_INVERT); // Turn off Mist coolant
+ #endif
+ #if ENABLED(COOLANT_FLOOD)
+ WRITE(COOLANT_FLOOD_PIN, COOLANT_FLOOD_INVERT); // Turn off Flood coolant
+ #endif
+}
+
+#endif // COOLANT_CONTROL
diff --git a/Marlin/src/gcode/control/M80_M81.cpp b/Marlin/src/gcode/control/M80_M81.cpp
new file mode 100644
index 0000000..394b06d
--- /dev/null
+++ b/Marlin/src/gcode/control/M80_M81.cpp
@@ -0,0 +1,113 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+
+#include "../../module/temperature.h"
+#include "../../module/planner.h" // for planner.finish_and_disable
+#include "../../module/printcounter.h" // for print_job_timer.stop
+#include "../../lcd/marlinui.h" // for LCD_MESSAGEPGM_P
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_SUICIDE
+ #include "../../MarlinCore.h"
+#endif
+
+#if ENABLED(PSU_CONTROL)
+
+ #if ENABLED(AUTO_POWER_CONTROL)
+ #include "../../feature/power.h"
+ #else
+ void restore_stepper_drivers();
+ #endif
+
+ // Could be moved to a feature, but this is all the data
+ bool powersupply_on;
+
+ #if HAS_TRINAMIC_CONFIG
+ #include "../../feature/tmc_util.h"
+ #endif
+
+ /**
+ * M80 : Turn on the Power Supply
+ * M80 S : Report the current state and exit
+ */
+ void GcodeSuite::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, !SUICIDE_PIN_INVERTING);
+ #endif
+
+ #if DISABLED(AUTO_POWER_CONTROL)
+ safe_delay(PSU_POWERUP_DELAY);
+ restore_stepper_drivers();
+ TERN_(HAS_TRINAMIC_CONFIG, safe_delay(PSU_POWERUP_DELAY));
+ #endif
+
+ TERN_(HAS_LCD_MENU, ui.reset_status());
+ }
+
+#endif // PSU_CONTROL
+
+/**
+ * M81: Turn off Power, including Power Supply, if there is one.
+ *
+ * This code should ALWAYS be available for FULL SHUTDOWN!
+ */
+void GcodeSuite::M81() {
+ thermalManager.disable_all_heaters();
+ planner.finish_and_disable();
+
+ print_job_timer.stop();
+
+ #if HAS_FAN
+ thermalManager.zero_fan_speeds();
+ #if ENABLED(PROBING_FANS_OFF)
+ thermalManager.fans_paused = false;
+ ZERO(thermalManager.saved_fan_speed);
+ #endif
+ #endif
+
+ safe_delay(1000); // Wait 1 second before switching off
+
+ #if HAS_SUICIDE
+ suicide();
+ #elif ENABLED(PSU_CONTROL)
+ PSU_OFF_SOON();
+ #endif
+
+ LCD_MESSAGEPGM_P(PSTR(MACHINE_NAME " " STR_OFF "."));
+}
diff --git a/Marlin/src/gcode/control/M85.cpp b/Marlin/src/gcode/control/M85.cpp
new file mode 100644
index 0000000..9c8c02c
--- /dev/null
+++ b/Marlin/src/gcode/control/M85.cpp
@@ -0,0 +1,35 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+
+/**
+ * M85: Set inactivity shutdown timer with parameter S<seconds>. To disable set zero (default)
+ */
+void GcodeSuite::M85() {
+
+ if (parser.seen('S')) {
+ reset_stepper_timeout();
+ max_inactive_time = parser.value_millis_from_seconds();
+ }
+
+}
diff --git a/Marlin/src/gcode/control/M993_M994.cpp b/Marlin/src/gcode/control/M993_M994.cpp
new file mode 100644
index 0000000..ff9ff85
--- /dev/null
+++ b/Marlin/src/gcode/control/M993_M994.cpp
@@ -0,0 +1,88 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ALL(HAS_SPI_FLASH, SDSUPPORT, MARLIN_DEV_MODE)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+#include "../../libs/W25Qxx.h"
+
+/**
+ * M993: Backup SPI Flash to SD
+ */
+void GcodeSuite::M993() {
+ if (!card.isMounted()) card.mount();
+
+ char fname[] = "spiflash.bin";
+ card.openFileWrite(fname);
+ if (!card.isFileOpen()) {
+ SERIAL_ECHOLNPAIR("Failed to open ", fname, " to write.");
+ return;
+ }
+
+ uint8_t buf[1024];
+ uint32_t addr = 0;
+ W25QXX.init(SPI_QUARTER_SPEED);
+ SERIAL_ECHOPGM("Save SPI Flash");
+ while (addr < SPI_FLASH_SIZE) {
+ W25QXX.SPI_FLASH_BufferRead(buf, addr, COUNT(buf));
+ addr += COUNT(buf);
+ card.write(buf, COUNT(buf));
+ if (addr % (COUNT(buf) * 10) == 0) SERIAL_CHAR('.');
+ }
+ SERIAL_ECHOLNPGM(" done");
+
+ card.closefile();
+}
+
+/**
+ * M994: Load a backup from SD to SPI Flash
+ */
+void GcodeSuite::M994() {
+ if (!card.isMounted()) card.mount();
+
+ char fname[] = "spiflash.bin";
+ card.openFileRead(fname);
+ if (!card.isFileOpen()) {
+ SERIAL_ECHOLNPAIR("Failed to open ", fname, " to read.");
+ return;
+ }
+
+ uint8_t buf[1024];
+ uint32_t addr = 0;
+ W25QXX.init(SPI_QUARTER_SPEED);
+ W25QXX.SPI_FLASH_BulkErase();
+ SERIAL_ECHOPGM("Load SPI Flash");
+ while (addr < SPI_FLASH_SIZE) {
+ card.read(buf, COUNT(buf));
+ W25QXX.SPI_FLASH_BufferWrite(buf, addr, COUNT(buf));
+ addr += COUNT(buf);
+ if (addr % (COUNT(buf) * 10) == 0) SERIAL_CHAR('.');
+ }
+ SERIAL_ECHOLNPGM(" done");
+
+ card.closefile();
+}
+
+#endif // HAS_SPI_FLASH && SDSUPPORT && MARLIN_DEV_MODE
diff --git a/Marlin/src/gcode/control/M997.cpp b/Marlin/src/gcode/control/M997.cpp
new file mode 100644
index 0000000..cdff96f
--- /dev/null
+++ b/Marlin/src/gcode/control/M997.cpp
@@ -0,0 +1,36 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+
+#if ENABLED(PLATFORM_M997_SUPPORT)
+
+/**
+ * M997: Perform in-application firmware update
+ */
+void GcodeSuite::M997() {
+
+ flashFirmware(parser.intval('S'));
+
+}
+
+#endif
diff --git a/Marlin/src/gcode/control/M999.cpp b/Marlin/src/gcode/control/M999.cpp
new file mode 100644
index 0000000..7487b4c
--- /dev/null
+++ b/Marlin/src/gcode/control/M999.cpp
@@ -0,0 +1,45 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+
+#include "../../lcd/marlinui.h" // for lcd_reset_alert_level
+#include "../../MarlinCore.h" // for marlin_state
+#include "../queue.h" // for flush_and_request_resend
+
+/**
+ * M999: Restart after being stopped
+ *
+ * Default behavior 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.
+ */
+void GcodeSuite::M999() {
+ marlin_state = MF_RUNNING;
+ ui.reset_alert_level();
+
+ if (parser.boolval('S')) return;
+
+ queue.flush_and_request_resend();
+}
diff --git a/Marlin/src/gcode/control/T.cpp b/Marlin/src/gcode/control/T.cpp
new file mode 100644
index 0000000..3ce284f
--- /dev/null
+++ b/Marlin/src/gcode/control/T.cpp
@@ -0,0 +1,70 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/tool_change.h"
+
+#if EITHER(HAS_MULTI_EXTRUDER, DEBUG_LEVELING_FEATURE)
+ #include "../../module/motion.h"
+#endif
+
+#if HAS_PRUSA_MMU2
+ #include "../../feature/mmu/mmu2.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
+#include "../../core/debug_out.h"
+
+/**
+ * T0-T<n>: Switch tool, usually switching extruders
+ *
+ * F[units/min] Set the movement feedrate
+ * S1 Don't move the tool in XY after change
+ *
+ * For PRUSA_MMU2(S) and SMUFF_EMU_MMU2(S)
+ * T[n] Gcode to extrude at least 38.10 mm at feedrate 19.02 mm/s must follow immediately to load to extruder wheels.
+ * T? Gcode to extrude shouldn't have to follow. Load to extruder wheels is done automatically.
+ * Tx Same as T?, but nozzle doesn't have to be preheated. Tc requires a preheated nozzle to finish filament load.
+ * Tc Load to nozzle after filament was prepared by Tc and nozzle is already heated.
+ */
+void GcodeSuite::T(const int8_t tool_index) {
+
+ DEBUG_SECTION(log_T, "T", DEBUGGING(LEVELING));
+ if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("...(", tool_index, ")");
+
+ // Count this command as movement / activity
+ reset_stepper_timeout();
+
+ #if HAS_PRUSA_MMU2
+ if (parser.string_arg) {
+ mmu2.tool_change(parser.string_arg); // Special commands T?/Tx/Tc
+ return;
+ }
+ #endif
+
+ tool_change(tool_index
+ #if HAS_MULTI_EXTRUDER
+ , TERN(PARKING_EXTRUDER, false, tool_index == active_extruder) // For PARKING_EXTRUDER motion is decided in tool_change()
+ || parser.boolval('S')
+ #endif
+ );
+}
diff --git a/Marlin/src/gcode/eeprom/M500-M504.cpp b/Marlin/src/gcode/eeprom/M500-M504.cpp
new file mode 100644
index 0000000..26c50a6
--- /dev/null
+++ b/Marlin/src/gcode/eeprom/M500-M504.cpp
@@ -0,0 +1,104 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/settings.h"
+#include "../../core/serial.h"
+#include "../../inc/MarlinConfig.h"
+
+/**
+ * M500: Store settings in EEPROM
+ */
+void GcodeSuite::M500() {
+ (void)settings.save();
+}
+
+/**
+ * M501: Read settings from EEPROM
+ */
+void GcodeSuite::M501() {
+ (void)settings.load();
+}
+
+/**
+ * M502: Revert to default settings
+ */
+void GcodeSuite::M502() {
+ (void)settings.reset();
+}
+
+#if DISABLED(DISABLE_M503)
+
+ /**
+ * M503: print settings currently in memory
+ */
+ void GcodeSuite::M503() {
+ (void)settings.report(!parser.boolval('S', true));
+ }
+
+#endif // !DISABLE_M503
+
+#if ENABLED(EEPROM_SETTINGS)
+
+ #if ENABLED(MARLIN_DEV_MODE)
+ #include "../../libs/hex_print.h"
+ #endif
+
+ /**
+ * M504: Validate EEPROM Contents
+ */
+ void GcodeSuite::M504() {
+ #if ENABLED(MARLIN_DEV_MODE)
+ const bool dowrite = parser.seenval('W');
+ if (dowrite || parser.seenval('R')) {
+ uint8_t val = 0;
+ int addr = parser.value_ushort();
+ if (dowrite) {
+ val = parser.byteval('V');
+ persistentStore.write_data(addr, &val);
+ SERIAL_ECHOLNPAIR("Wrote address ", addr, " with ", int(val));
+ }
+ else {
+ if (parser.seenval('T')) {
+ const int endaddr = parser.value_ushort();
+ while (addr <= endaddr) {
+ persistentStore.read_data(addr, &val);
+ SERIAL_ECHOLNPAIR("0x", hex_word(addr), ":", hex_byte(val));
+ addr++;
+ safe_delay(10);
+ }
+ SERIAL_EOL();
+ }
+ else {
+ persistentStore.read_data(addr, &val);
+ SERIAL_ECHOLNPAIR("Read address ", addr, " and got ", int(val));
+ }
+ }
+ return;
+ }
+ #endif
+
+ if (settings.validate())
+ SERIAL_ECHO_MSG("EEPROM OK");
+ }
+
+#endif
diff --git a/Marlin/src/gcode/feature/L6470/M122.cpp b/Marlin/src/gcode/feature/L6470/M122.cpp
new file mode 100644
index 0000000..d2b7f73
--- /dev/null
+++ b/Marlin/src/gcode/feature/L6470/M122.cpp
@@ -0,0 +1,151 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_L64XX
+
+#include "../../gcode.h"
+#include "../../../libs/L64XX/L64XX_Marlin.h"
+#include "../../../module/stepper/indirection.h"
+
+void echo_yes_no(const bool yes);
+
+inline void L6470_say_status(const L64XX_axis_t axis) {
+ if (L64xxManager.spi_abort) return;
+ const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow;
+ L64xxManager.get_status(axis);
+ L64xxManager.say_axis(axis);
+ #if ENABLED(L6470_CHITCHAT)
+ char temp_buf[20];
+ sprintf_P(temp_buf, PSTR(" status: %4x "), sh.STATUS_AXIS_RAW);
+ SERIAL_ECHO(temp_buf);
+ print_bin(sh.STATUS_AXIS_RAW);
+ switch (sh.STATUS_AXIS_LAYOUT) {
+ case L6470_STATUS_LAYOUT: serialprintPGM(PSTR(" L6470")); break;
+ case L6474_STATUS_LAYOUT: serialprintPGM(PSTR(" L6474")); break;
+ case L6480_STATUS_LAYOUT: serialprintPGM(PSTR(" L6480/powerSTEP01")); break;
+ }
+ #endif
+ SERIAL_ECHOPGM("\n...OUTPUT: ");
+ serialprintPGM(sh.STATUS_AXIS & STATUS_HIZ ? PSTR("OFF") : PSTR("ON "));
+ SERIAL_ECHOPGM(" BUSY: "); echo_yes_no((sh.STATUS_AXIS & STATUS_BUSY) == 0);
+ SERIAL_ECHOPGM(" DIR: ");
+ serialprintPGM((((sh.STATUS_AXIS & STATUS_DIR) >> 4) ^ L64xxManager.index_to_dir[axis]) ? PSTR("FORWARD") : PSTR("REVERSE"));
+ if (sh.STATUS_AXIS_LAYOUT == L6480_STATUS_LAYOUT) {
+ SERIAL_ECHOPGM(" Last Command: ");
+ if (sh.STATUS_AXIS & sh.STATUS_AXIS_WRONG_CMD) SERIAL_ECHOPGM("VALID");
+ else SERIAL_ECHOPGM("ERROR");
+ SERIAL_ECHOPGM("\n...THERMAL: ");
+ switch ((sh.STATUS_AXIS & (sh.STATUS_AXIS_TH_SD | sh.STATUS_AXIS_TH_WRN)) >> 11) {
+ case 0: SERIAL_ECHOPGM("DEVICE SHUTDOWN"); break;
+ case 1: SERIAL_ECHOPGM("BRIDGE SHUTDOWN"); break;
+ case 2: SERIAL_ECHOPGM("WARNING "); break;
+ case 3: SERIAL_ECHOPGM("OK "); break;
+ }
+ }
+ else {
+ SERIAL_ECHOPGM(" Last Command: ");
+ if (!(sh.STATUS_AXIS & sh.STATUS_AXIS_WRONG_CMD)) SERIAL_ECHOPGM("IN");
+ SERIAL_ECHOPGM("VALID ");
+ serialprintPGM(sh.STATUS_AXIS & sh.STATUS_AXIS_NOTPERF_CMD ? PSTR("COMPLETED ") : PSTR("Not PERFORMED"));
+ SERIAL_ECHOPAIR("\n...THERMAL: ", !(sh.STATUS_AXIS & sh.STATUS_AXIS_TH_SD) ? "SHUTDOWN " : !(sh.STATUS_AXIS & sh.STATUS_AXIS_TH_WRN) ? "WARNING " : "OK ");
+ }
+ SERIAL_ECHOPGM(" OVERCURRENT:"); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_OCD) == 0);
+ if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) {
+ SERIAL_ECHOPGM(" STALL:"); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_STEP_LOSS_A) == 0 || (sh.STATUS_AXIS & sh.STATUS_AXIS_STEP_LOSS_B) == 0);
+ SERIAL_ECHOPGM(" STEP-CLOCK MODE:"); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_SCK_MOD) != 0);
+ }
+ else {
+ SERIAL_ECHOPGM(" STALL: NA "
+ " STEP-CLOCK MODE: NA"
+ " UNDER VOLTAGE LOCKOUT: "); echo_yes_no((sh.STATUS_AXIS & sh.STATUS_AXIS_UVLO) == 0);
+ }
+ SERIAL_EOL();
+}
+
+/**
+ * M122: Debug L6470 drivers
+ */
+void GcodeSuite::M122() {
+ L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status
+ L64xxManager.spi_active = true; // Tell set_directions() a series of SPI transfers is underway
+
+ //if (parser.seen('S'))
+ // tmc_set_report_interval(parser.value_bool());
+ //else
+
+ #if AXIS_IS_L64XX(X)
+ L6470_say_status(X);
+ #endif
+ #if AXIS_IS_L64XX(X2)
+ L6470_say_status(X2);
+ #endif
+ #if AXIS_IS_L64XX(Y)
+ L6470_say_status(Y);
+ #endif
+ #if AXIS_IS_L64XX(Y2)
+ L6470_say_status(Y2);
+ #endif
+ #if AXIS_IS_L64XX(Z)
+ L6470_say_status(Z);
+ #endif
+ #if AXIS_IS_L64XX(Z2)
+ L6470_say_status(Z2);
+ #endif
+ #if AXIS_IS_L64XX(Z3)
+ L6470_say_status(Z3);
+ #endif
+ #if AXIS_IS_L64XX(Z4)
+ L6470_say_status(Z4);
+ #endif
+ #if AXIS_IS_L64XX(E0)
+ L6470_say_status(E0);
+ #endif
+ #if AXIS_IS_L64XX(E1)
+ L6470_say_status(E1);
+ #endif
+ #if AXIS_IS_L64XX(E2)
+ L6470_say_status(E2);
+ #endif
+ #if AXIS_IS_L64XX(E3)
+ L6470_say_status(E3);
+ #endif
+ #if AXIS_IS_L64XX(E4)
+ L6470_say_status(E4);
+ #endif
+ #if AXIS_IS_L64XX(E5)
+ L6470_say_status(E5);
+ #endif
+ #if AXIS_IS_L64XX(E6)
+ L6470_say_status(E6);
+ #endif
+ #if AXIS_IS_L64XX(E7)
+ L6470_say_status(E7);
+ #endif
+
+ L64xxManager.spi_active = false; // done with all SPI transfers - clear handshake flags
+ L64xxManager.spi_abort = false;
+ L64xxManager.pause_monitor(false);
+}
+
+#endif // HAS_L64XX
diff --git a/Marlin/src/gcode/feature/L6470/M906.cpp b/Marlin/src/gcode/feature/L6470/M906.cpp
new file mode 100644
index 0000000..7bd446a
--- /dev/null
+++ b/Marlin/src/gcode/feature/L6470/M906.cpp
@@ -0,0 +1,370 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_L64XX
+
+#include "../../gcode.h"
+#include "../../../libs/L64XX/L64XX_Marlin.h"
+#include "../../../module/stepper/indirection.h"
+#include "../../../module/planner.h"
+
+#define DEBUG_OUT ENABLED(L6470_CHITCHAT)
+#include "../../../core/debug_out.h"
+
+/**
+ * M906: report or set KVAL_HOLD which sets the maximum effective voltage provided by the
+ * PWMs to the steppers
+ *
+ * On L6474 this sets the TVAL register (same address).
+ *
+ * I - select which driver(s) to change on multi-driver axis
+ * 0 - (default) all drivers on the axis or E0
+ * 1 - monitor only X, Y, Z or E1
+ * 2 - monitor only X2, Y2, Z2 or E2
+ * 3 - monitor only Z3 or E3
+ * 4 - monitor only Z4 or E4
+ * 5 - monitor only E5
+ * Xxxx, Yxxx, Zxxx, Exxx - axis to change (optional)
+ * L6474 - current in mA (4A max)
+ * All others - 0-255
+ */
+
+/**
+ * Sets KVAL_HOLD wich affects the current being driven through the stepper.
+ *
+ * L6470 is used in the STEP-CLOCK mode. KVAL_HOLD is the only KVAL_xxx
+ * that affects the effective voltage seen by the stepper.
+ */
+
+/**
+ * MACRO to fetch information on the items associated with current limiting
+ * and maximum voltage output.
+ *
+ * L6470 can be setup to shutdown if either current threshold is exceeded.
+ *
+ * L6470 output current can not be set directly. It is set indirectly by
+ * setting the maximum effective output voltage.
+ *
+ * Effective output voltage is set by PWM duty cycle.
+ *
+ * Maximum effective output voltage is affected by MANY variables. The main ones are:
+ * KVAL_HOLD
+ * KVAL_RUN
+ * KVAL_ACC
+ * KVAL_DEC
+ * Vs compensation (if enabled)
+ */
+void L64XX_report_current(L64XX &motor, const L64XX_axis_t axis) {
+
+ if (L64xxManager.spi_abort) return; // don't do anything if set_directions() has occurred
+
+ const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow;
+ const uint16_t status = L64xxManager.get_status(axis); //also populates shadow structure
+ const uint8_t OverCurrent_Threshold = uint8_t(motor.GetParam(L6470_OCD_TH));
+
+ auto say_axis_status = [](const L64XX_axis_t axis, const uint16_t status) {
+ L64xxManager.say_axis(axis);
+ #if ENABLED(L6470_CHITCHAT)
+ char tmp[10];
+ sprintf_P(tmp, PSTR("%4x "), status);
+ DEBUG_ECHOPAIR(" status: ", tmp);
+ print_bin(status);
+ #else
+ UNUSED(status);
+ #endif
+ SERIAL_EOL();
+ };
+
+ char temp_buf[10];
+
+ switch (sh.STATUS_AXIS_LAYOUT) {
+ case L6470_STATUS_LAYOUT: // L6470
+ case L6480_STATUS_LAYOUT: { // L6480 & powerstep01
+ const uint16_t Stall_Threshold = (uint8_t)motor.GetParam(L6470_STALL_TH),
+ motor_status = (status & (STATUS_MOT_STATUS)) >> 5,
+ L6470_ADC_out = motor.GetParam(L6470_ADC_OUT),
+ L6470_ADC_out_limited = constrain(L6470_ADC_out, 8, 24);
+ const float comp_coef = 1600.0f / L6470_ADC_out_limited;
+ const uint16_t MicroSteps = _BV(motor.GetParam(L6470_STEP_MODE) & 0x07);
+
+ say_axis_status(axis, sh.STATUS_AXIS_RAW);
+
+ SERIAL_ECHOPGM("...OverCurrent Threshold: ");
+ sprintf_P(temp_buf, PSTR("%2d ("), OverCurrent_Threshold);
+ SERIAL_ECHO(temp_buf);
+ SERIAL_ECHO((OverCurrent_Threshold + 1) * motor.OCD_CURRENT_CONSTANT_INV);
+ SERIAL_ECHOPGM(" mA)");
+ SERIAL_ECHOPGM(" Stall Threshold: ");
+ sprintf_P(temp_buf, PSTR("%2d ("), Stall_Threshold);
+ SERIAL_ECHO(temp_buf);
+ SERIAL_ECHO((Stall_Threshold + 1) * motor.STALL_CURRENT_CONSTANT_INV);
+ SERIAL_ECHOPGM(" mA)");
+ SERIAL_ECHOPGM(" Motor Status: ");
+ switch (motor_status) {
+ case 0: SERIAL_ECHOPGM("stopped"); break;
+ case 1: SERIAL_ECHOPGM("accelerating"); break;
+ case 2: SERIAL_ECHOPGM("decelerating"); break;
+ case 3: SERIAL_ECHOPGM("at constant speed"); break;
+ }
+ SERIAL_EOL();
+
+ SERIAL_ECHOPAIR("...MicroSteps: ", MicroSteps,
+ " ADC_OUT: ", L6470_ADC_out);
+ SERIAL_ECHOPGM(" Vs_compensation: ");
+ serialprintPGM((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_EN_VSCOMP) ? PSTR("ENABLED ") : PSTR("DISABLED"));
+ SERIAL_ECHOLNPAIR(" Compensation coefficient: ~", comp_coef * 0.01f);
+
+ SERIAL_ECHOPAIR("...KVAL_HOLD: ", motor.GetParam(L6470_KVAL_HOLD),
+ " KVAL_RUN : ", motor.GetParam(L6470_KVAL_RUN),
+ " KVAL_ACC: ", motor.GetParam(L6470_KVAL_ACC),
+ " KVAL_DEC: ", motor.GetParam(L6470_KVAL_DEC),
+ " V motor max = ");
+ switch (motor_status) {
+ case 0: SERIAL_ECHO(motor.GetParam(L6470_KVAL_HOLD) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_HOLD)"); break;
+ case 1: SERIAL_ECHO(motor.GetParam(L6470_KVAL_RUN) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_RUN)"); break;
+ case 2: SERIAL_ECHO(motor.GetParam(L6470_KVAL_ACC) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_ACC)"); break;
+ case 3: SERIAL_ECHO(motor.GetParam(L6470_KVAL_DEC) * 100 / 256); SERIAL_ECHOPGM("% (KVAL_HOLD)"); break;
+ }
+ SERIAL_EOL();
+
+ #if ENABLED(L6470_CHITCHAT)
+ DEBUG_ECHOPGM("...SLEW RATE: ");
+ switch (sh.STATUS_AXIS_LAYOUT) {
+ case L6470_STATUS_LAYOUT: {
+ switch ((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT) {
+ case 0: { DEBUG_ECHOLNPGM("320V/uS") ; break; }
+ case 1: { DEBUG_ECHOLNPGM("75V/uS") ; break; }
+ case 2: { DEBUG_ECHOLNPGM("110V/uS") ; break; }
+ case 3: { DEBUG_ECHOLNPGM("260V/uS") ; break; }
+ }
+ break;
+ }
+ case L6480_STATUS_LAYOUT: {
+ switch (motor.GetParam(L6470_GATECFG1) & CONFIG1_SR ) {
+ case CONFIG1_SR_220V_us: { DEBUG_ECHOLNPGM("220V/uS") ; break; }
+ case CONFIG1_SR_400V_us: { DEBUG_ECHOLNPGM("400V/uS") ; break; }
+ case CONFIG1_SR_520V_us: { DEBUG_ECHOLNPGM("520V/uS") ; break; }
+ case CONFIG1_SR_980V_us: { DEBUG_ECHOLNPGM("980V/uS") ; break; }
+ default: { DEBUG_ECHOLNPGM("unknown") ; break; }
+ }
+ }
+ }
+ #endif
+ SERIAL_EOL();
+ break;
+ }
+
+ case L6474_STATUS_LAYOUT: { // L6474
+ const uint16_t L6470_ADC_out = motor.GetParam(L6470_ADC_OUT) & 0x1F,
+ L6474_TVAL_val = motor.GetParam(L6474_TVAL) & 0x7F;
+
+ say_axis_status(axis, sh.STATUS_AXIS_RAW);
+
+ SERIAL_ECHOPGM("...OverCurrent Threshold: ");
+ sprintf_P(temp_buf, PSTR("%2d ("), OverCurrent_Threshold);
+ SERIAL_ECHO(temp_buf);
+ SERIAL_ECHO((OverCurrent_Threshold + 1) * motor.OCD_CURRENT_CONSTANT_INV);
+ SERIAL_ECHOPGM(" mA)");
+ SERIAL_ECHOPGM(" TVAL: ");
+ sprintf_P(temp_buf, PSTR("%2d ("), L6474_TVAL_val);
+ SERIAL_ECHO(temp_buf);
+ SERIAL_ECHO((L6474_TVAL_val + 1) * motor.STALL_CURRENT_CONSTANT_INV);
+ SERIAL_ECHOLNPGM(" mA) Motor Status: NA");
+
+ const uint16_t MicroSteps = _BV(motor.GetParam(L6470_STEP_MODE) & 0x07); //NOMORE(MicroSteps, 16);
+ SERIAL_ECHOPAIR("...MicroSteps: ", MicroSteps,
+ " ADC_OUT: ", L6470_ADC_out);
+
+ SERIAL_ECHOLNPGM(" Vs_compensation: NA\n");
+ SERIAL_ECHOLNPGM("...KVAL_HOLD: NA"
+ " KVAL_RUN : NA"
+ " KVAL_ACC: NA"
+ " KVAL_DEC: NA"
+ " V motor max = NA");
+
+ #if ENABLED(L6470_CHITCHAT)
+ DEBUG_ECHOPGM("...SLEW RATE: ");
+ switch ((motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT) {
+ case 0: DEBUG_ECHOLNPGM("320V/uS") ; break;
+ case 1: DEBUG_ECHOLNPGM("75V/uS") ; break;
+ case 2: DEBUG_ECHOLNPGM("110V/uS") ; break;
+ case 3: DEBUG_ECHOLNPGM("260V/uS") ; break;
+ default: DEBUG_ECHOLNPAIR("slew rate: ", (motor.GetParam(sh.L6470_AXIS_CONFIG) & CONFIG_POW_SR) >> CONFIG_POW_SR_BIT); break;
+ }
+ #endif
+ SERIAL_EOL();
+ SERIAL_EOL();
+ break;
+ }
+ }
+}
+
+void GcodeSuite::M906() {
+
+ L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status
+
+ #define L6470_SET_KVAL_HOLD(Q) (AXIS_IS_L64XX(Q) ? stepper##Q.setTVALCurrent(value) : stepper##Q.SetParam(L6470_KVAL_HOLD, uint8_t(value)))
+
+ DEBUG_ECHOLNPGM("M906");
+
+ uint8_t report_current = true;
+
+ #if HAS_L64XX
+ const uint8_t index = parser.byteval('I');
+ #endif
+
+ LOOP_XYZE(i) if (uint16_t value = parser.intval(axis_codes[i])) {
+
+ report_current = false;
+
+ if (planner.has_blocks_queued() || planner.cleaning_buffer_counter) {
+ SERIAL_ECHOLNPGM("Test aborted. Can't set KVAL_HOLD while steppers are moving.");
+ return;
+ }
+
+ switch (i) {
+ case X_AXIS:
+ #if AXIS_IS_L64XX(X)
+ if (index == 0) L6470_SET_KVAL_HOLD(X);
+ #endif
+ #if AXIS_IS_L64XX(X2)
+ if (index == 1) L6470_SET_KVAL_HOLD(X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if AXIS_IS_L64XX(Y)
+ if (index == 0) L6470_SET_KVAL_HOLD(Y);
+ #endif
+ #if AXIS_IS_L64XX(Y2)
+ if (index == 1) L6470_SET_KVAL_HOLD(Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if AXIS_IS_L64XX(Z)
+ if (index == 0) L6470_SET_KVAL_HOLD(Z);
+ #endif
+ #if AXIS_IS_L64XX(Z2)
+ if (index == 1) L6470_SET_KVAL_HOLD(Z2);
+ #endif
+ #if AXIS_IS_L64XX(Z3)
+ if (index == 2) L6470_SET_KVAL_HOLD(Z3);
+ #endif
+ #if AXIS_DRIVER_TYPE_Z4(L6470)
+ if (index == 3) L6470_SET_KVAL_HOLD(Z4);
+ #endif
+ break;
+ case E_AXIS: {
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ switch (target_extruder) {
+ #if AXIS_IS_L64XX(E0)
+ case 0: L6470_SET_KVAL_HOLD(E0); break;
+ #endif
+ #if AXIS_IS_L64XX(E1)
+ case 1: L6470_SET_KVAL_HOLD(E1); break;
+ #endif
+ #if AXIS_IS_L64XX(E2)
+ case 2: L6470_SET_KVAL_HOLD(E2); break;
+ #endif
+ #if AXIS_IS_L64XX(E3)
+ case 3: L6470_SET_KVAL_HOLD(E3); break;
+ #endif
+ #if AXIS_IS_L64XX(E4)
+ case 4: L6470_SET_KVAL_HOLD(E4); break;
+ #endif
+ #if AXIS_IS_L64XX(E5)
+ case 5: L6470_SET_KVAL_HOLD(E5); break;
+ #endif
+ #if AXIS_IS_L64XX(E6)
+ case 6: L6470_SET_KVAL_HOLD(E6); break;
+ #endif
+ #if AXIS_IS_L64XX(E7)
+ case 7: L6470_SET_KVAL_HOLD(E7); break;
+ #endif
+ }
+ } break;
+ }
+ }
+
+ if (report_current) {
+ #define L64XX_REPORT_CURRENT(Q) L64XX_report_current(stepper##Q, Q)
+
+ L64xxManager.spi_active = true; // Tell set_directions() a series of SPI transfers is underway
+
+ #if AXIS_IS_L64XX(X)
+ L64XX_REPORT_CURRENT(X);
+ #endif
+ #if AXIS_IS_L64XX(X2)
+ L64XX_REPORT_CURRENT(X2);
+ #endif
+ #if AXIS_IS_L64XX(Y)
+ L64XX_REPORT_CURRENT(Y);
+ #endif
+ #if AXIS_IS_L64XX(Y2)
+ L64XX_REPORT_CURRENT(Y2);
+ #endif
+ #if AXIS_IS_L64XX(Z)
+ L64XX_REPORT_CURRENT(Z);
+ #endif
+ #if AXIS_IS_L64XX(Z2)
+ L64XX_REPORT_CURRENT(Z2);
+ #endif
+ #if AXIS_IS_L64XX(Z3)
+ L64XX_REPORT_CURRENT(Z3);
+ #endif
+ #if AXIS_IS_L64XX(Z4)
+ L64XX_REPORT_CURRENT(Z4);
+ #endif
+ #if AXIS_IS_L64XX(E0)
+ L64XX_REPORT_CURRENT(E0);
+ #endif
+ #if AXIS_IS_L64XX(E1)
+ L64XX_REPORT_CURRENT(E1);
+ #endif
+ #if AXIS_IS_L64XX(E2)
+ L64XX_REPORT_CURRENT(E2);
+ #endif
+ #if AXIS_IS_L64XX(E3)
+ L64XX_REPORT_CURRENT(E3);
+ #endif
+ #if AXIS_IS_L64XX(E4)
+ L64XX_REPORT_CURRENT(E4);
+ #endif
+ #if AXIS_IS_L64XX(E5)
+ L64XX_REPORT_CURRENT(E5);
+ #endif
+ #if AXIS_IS_L64XX(E6)
+ L64XX_REPORT_CURRENT(E6);
+ #endif
+ #if AXIS_IS_L64XX(E7)
+ L64XX_REPORT_CURRENT(E7);
+ #endif
+
+ L64xxManager.spi_active = false; // done with all SPI transfers - clear handshake flags
+ L64xxManager.spi_abort = false;
+ L64xxManager.pause_monitor(false);
+ }
+}
+
+#endif // HAS_L64XX
diff --git a/Marlin/src/gcode/feature/L6470/M916-918.cpp b/Marlin/src/gcode/feature/L6470/M916-918.cpp
new file mode 100644
index 0000000..8a1ea48
--- /dev/null
+++ b/Marlin/src/gcode/feature/L6470/M916-918.cpp
@@ -0,0 +1,651 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+//
+// NOTE: All tests assume each axis uses matching driver chips.
+//
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_L64XX
+
+#include "../../gcode.h"
+#include "../../../module/stepper/indirection.h"
+#include "../../../module/planner.h"
+#include "../../../libs/L64XX/L64XX_Marlin.h"
+
+#define DEBUG_OUT ENABLED(L6470_CHITCHAT)
+#include "../../../core/debug_out.h"
+
+/**
+ * M916: increase KVAL_HOLD until get thermal warning
+ * NOTE - on L6474 it is TVAL that is used
+ *
+ * J - select which driver(s) to monitor on multi-driver axis
+ * 0 - (default) monitor all drivers on the axis or E0
+ * 1 - monitor only X, Y, Z, E1
+ * 2 - monitor only X2, Y2, Z2, E2
+ * 3 - monitor only Z3, E3
+ * 4 - monitor only Z4, E4
+ *
+ * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement
+ * xxx (1-255) is distance moved on either side of current position
+ *
+ * F - feedrate
+ * optional - will use default max feedrate from configuration.h if not specified
+ *
+ * T - current (mA) setting for TVAL (0 - 4A in 31.25mA increments, rounds down) - L6474 only
+ * optional - will report current value from driver if not specified
+ *
+ * K - value for KVAL_HOLD (0 - 255) (ignored for L6474)
+ * optional - will report current value from driver if not specified
+ *
+ * D - time (in seconds) to run each setting of KVAL_HOLD/TVAL
+ * optional - defaults to zero (runs each setting once)
+ */
+
+/**
+ * This routine is also useful for determining the approximate KVAL_HOLD
+ * where the stepper stops losing steps. The sound will get noticeably quieter
+ * as it stops losing steps.
+ */
+
+void GcodeSuite::M916() {
+
+ DEBUG_ECHOLNPGM("M916");
+
+ L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status
+
+ // Variables used by L64xxManager.get_user_input function - some may not be used
+ char axis_mon[3][3] = { {" "}, {" "}, {" "} }; // list of Axes to be monitored
+ L64XX_axis_t axis_index[3];
+ uint16_t axis_status[3];
+ uint8_t driver_count = 1;
+ float position_max;
+ float position_min;
+ float final_feedrate;
+ uint8_t kval_hold;
+ uint8_t OCD_TH_val = 0;
+ uint8_t STALL_TH_val = 0;
+ uint16_t over_current_threshold;
+ constexpr uint8_t over_current_flag = false; // M916 doesn't play with the overcurrent thresholds
+
+ #define DRIVER_TYPE_L6474(Q) AXIS_DRIVER_TYPE_##Q(L6474)
+
+ uint8_t j; // general purpose counter
+
+ if (L64xxManager.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, OCD_TH_val, STALL_TH_val, over_current_threshold))
+ return; // quit if invalid user input
+
+ DEBUG_ECHOLNPAIR("feedrate = ", final_feedrate);
+
+ planner.synchronize(); // wait for all current movement commands to complete
+
+ const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow;
+ for (j = 0; j < driver_count; j++)
+ L64xxManager.get_status(axis_index[j]); // clear out any pre-existing error flags
+
+ char temp_axis_string[] = " ";
+ temp_axis_string[0] = axis_mon[0][0]; // need to have a string for use within sprintf format section
+ char gcode_string[80];
+ uint16_t status_composite = 0;
+
+ uint16_t M91x_counter = kval_hold;
+ uint16_t M91x_counter_max;
+ if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) {
+ M91x_counter_max = 128; // TVAL is 7 bits
+ LIMIT(M91x_counter, 0U, 127U);
+ }
+ else
+ M91x_counter_max = 256; // KVAL_HOLD is 8 bits
+
+ uint8_t M91x_delay_s = parser.byteval('D'); // get delay in seconds
+ millis_t M91x_delay_ms = SEC_TO_MS(M91x_delay_s * 60);
+ millis_t M91x_delay_end;
+
+ DEBUG_ECHOLNPGM(".\n.");
+
+ do {
+
+ if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT)
+ DEBUG_ECHOLNPAIR("TVAL current (mA) = ", (M91x_counter + 1) * sh.AXIS_STALL_CURRENT_CONSTANT_INV); // report TVAL current for this run
+ else
+ DEBUG_ECHOLNPAIR("kval_hold = ", M91x_counter); // report KVAL_HOLD for this run
+
+ for (j = 0; j < driver_count; j++)
+ L64xxManager.set_param(axis_index[j], L6470_KVAL_HOLD, M91x_counter); //set KVAL_HOLD or TVAL (same register address)
+
+ M91x_delay_end = millis() + M91x_delay_ms;
+ do {
+ // turn the motor(s) both directions
+ sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_min), uint16_t(final_feedrate));
+ gcode.process_subcommands_now_P(gcode_string);
+
+ sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_max), uint16_t(final_feedrate));
+ gcode.process_subcommands_now_P(gcode_string);
+
+ // get the status after the motors have stopped
+ planner.synchronize();
+
+ status_composite = 0; // clear out the old bits
+
+ for (j = 0; j < driver_count; j++) {
+ axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & sh.L6470_ERROR_MASK; // bits of interest are all active low
+ status_composite |= axis_status[j] ;
+ }
+
+ if (status_composite) break;
+ } while (millis() < M91x_delay_end);
+
+ if (status_composite) break;
+
+ M91x_counter++;
+
+ } while (!(status_composite & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD)) && (M91x_counter < M91x_counter_max));
+
+ DEBUG_ECHOLNPGM(".");
+
+ #if ENABLED(L6470_CHITCHAT)
+ if (status_composite) {
+ L64xxManager.error_status_decode(status_composite, axis_index[0],
+ sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN,
+ sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B,
+ sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT);
+ DEBUG_ECHOLNPGM(".");
+ }
+ #endif
+
+ if ((status_composite & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD)))
+ DEBUG_ECHOLNPGM(".\n.\nTest completed normally - Thermal warning/shutdown has occurred");
+ else if (status_composite)
+ DEBUG_ECHOLNPGM(".\n.\nTest completed abnormally - non-thermal error has occured");
+ else
+ DEBUG_ECHOLNPGM(".\n.\nTest completed normally - Unable to get to thermal warning/shutdown");
+
+ L64xxManager.pause_monitor(false);
+}
+
+/**
+ * M917: Find minimum current thresholds
+ *
+ * Decrease OCD current until overcurrent error
+ * Increase OCD until overcurrent error goes away
+ * Decrease stall threshold until stall (not done on L6474)
+ * Increase stall until stall error goes away (not done on L6474)
+ *
+ * J - select which driver(s) to monitor on multi-driver axis
+ * 0 - (default) monitor all drivers on the axis or E0
+ * 1 - monitor only X, Y, Z, E1
+ * 2 - monitor only X2, Y2, Z2, E2
+ * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement
+ * xxx (1-255) is distance moved on either side of current position
+ *
+ * F - feedrate
+ * optional - will use default max feedrate from Configuration.h if not specified
+ *
+ * I - starting over-current threshold
+ * optional - will report current value from driver if not specified
+ * if there are multiple drivers on the axis then all will be set the same
+ *
+ * T - current (mA) setting for TVAL (0 - 4A in 31.25mA increments, rounds down) - L6474 only
+ * optional - will report current value from driver if not specified
+ *
+ * K - value for KVAL_HOLD (0 - 255) (ignored for L6474)
+ * optional - will report current value from driver if not specified
+ */
+void GcodeSuite::M917() {
+
+ DEBUG_ECHOLNPGM("M917");
+
+ L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status
+
+ char axis_mon[3][3] = { {" "}, {" "}, {" "} }; // list of Axes to be monitored
+ L64XX_axis_t axis_index[3];
+ uint16_t axis_status[3];
+ uint8_t driver_count = 1;
+ float position_max;
+ float position_min;
+ float final_feedrate;
+ uint8_t kval_hold;
+ uint8_t OCD_TH_val = 0;
+ uint8_t STALL_TH_val = 0;
+ uint16_t over_current_threshold;
+ constexpr uint8_t over_current_flag = true;
+
+ uint8_t j; // general purpose counter
+
+ if (L64xxManager.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, OCD_TH_val, STALL_TH_val, over_current_threshold))
+ return; // quit if invalid user input
+
+ DEBUG_ECHOLNPAIR("feedrate = ", final_feedrate);
+
+ planner.synchronize(); // wait for all current movement commands to complete
+
+ const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow;
+ for (j = 0; j < driver_count; j++)
+ L64xxManager.get_status(axis_index[j]); // clear error flags
+ char temp_axis_string[] = " ";
+ temp_axis_string[0] = axis_mon[0][0]; // need a sprintf format string
+ char gcode_string[80];
+ uint16_t status_composite = 0;
+ uint8_t test_phase = 0; // 0 - decreasing OCD - exit when OCD warning occurs (ignore STALL)
+ // 1 - increasing OCD - exit when OCD warning stops (ignore STALL)
+ // 2 - OCD finalized - decreasing STALL - exit when STALL warning happens
+ // 3 - OCD finalized - increasing STALL - exit when STALL warning stop
+ // 4 - all testing completed
+ DEBUG_ECHOPAIR(".\n.\n.\nover_current threshold : ", (OCD_TH_val + 1) * 375); // first status display
+ DEBUG_ECHOPAIR(" (OCD_TH: : ", OCD_TH_val);
+ if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) {
+ DEBUG_ECHOPAIR(") Stall threshold: ", (STALL_TH_val + 1) * 31.25);
+ DEBUG_ECHOPAIR(" (STALL_TH: ", STALL_TH_val);
+ }
+ DEBUG_ECHOLNPGM(")");
+
+ do {
+
+ if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) DEBUG_ECHOPAIR("STALL threshold : ", (STALL_TH_val + 1) * 31.25);
+ DEBUG_ECHOLNPAIR(" OCD threshold : ", (OCD_TH_val + 1) * 375);
+
+ sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_min), uint16_t(final_feedrate));
+ gcode.process_subcommands_now_P(gcode_string);
+
+ sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_max), uint16_t(final_feedrate));
+ gcode.process_subcommands_now_P(gcode_string);
+
+ planner.synchronize();
+
+ status_composite = 0; // clear out the old bits
+
+ for (j = 0; j < driver_count; j++) {
+ axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & sh.L6470_ERROR_MASK; // bits of interest are all active low
+ status_composite |= axis_status[j];
+ }
+
+ if (status_composite && (status_composite & sh.STATUS_AXIS_UVLO)) {
+ DEBUG_ECHOLNPGM("Test aborted (Undervoltage lockout active)");
+ #if ENABLED(L6470_CHITCHAT)
+ for (j = 0; j < driver_count; j++) {
+ if (j) DEBUG_ECHOPGM("...");
+ L64xxManager.error_status_decode(axis_status[j], axis_index[j],
+ sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN,
+ sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B,
+ sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT);
+ }
+ #endif
+ return;
+ }
+
+ if (status_composite & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD)) {
+ DEBUG_ECHOLNPGM("thermal problem - waiting for chip(s) to cool down ");
+ uint16_t status_composite_temp = 0;
+ uint8_t k = 0;
+ do {
+ k++;
+ if (!(k % 4)) {
+ kval_hold *= 0.95;
+ DEBUG_EOL();
+ DEBUG_ECHOLNPAIR("Lowering KVAL_HOLD by about 5% to ", kval_hold);
+ for (j = 0; j < driver_count; j++)
+ L64xxManager.set_param(axis_index[j], L6470_KVAL_HOLD, kval_hold);
+ }
+ DEBUG_ECHOLNPGM(".");
+ gcode.reset_stepper_timeout(); // keep steppers powered
+ watchdog_refresh();
+ safe_delay(5000);
+ status_composite_temp = 0;
+ for (j = 0; j < driver_count; j++) {
+ axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & sh.L6470_ERROR_MASK; // bits of interest are all active low
+ status_composite_temp |= axis_status[j];
+ }
+ }
+ while (status_composite_temp & (sh.STATUS_AXIS_TH_WRN | sh.STATUS_AXIS_TH_SD));
+ DEBUG_EOL();
+ }
+ if (status_composite & (sh.STATUS_AXIS_STEP_LOSS_A | sh.STATUS_AXIS_STEP_LOSS_B | sh.STATUS_AXIS_OCD)) {
+ switch (test_phase) {
+
+ case 0: {
+ if (status_composite & sh.STATUS_AXIS_OCD) {
+ // phase 0 with OCD warning - time to go to next phase
+ if (OCD_TH_val >= sh.AXIS_OCD_TH_MAX) {
+ OCD_TH_val = sh.AXIS_OCD_TH_MAX; // limit to max
+ test_phase = 2; // at highest value so skip phase 1
+ //DEBUG_ECHOLNPGM("LOGIC E0A OCD at highest - skip to 2");
+ DEBUG_ECHOLNPGM("OCD at highest - OCD finalized");
+ }
+ else {
+ OCD_TH_val++; // normal exit to next phase
+ test_phase = 1; // setup for first pass of phase 1
+ //DEBUG_ECHOLNPGM("LOGIC E0B - inc OCD & go to 1");
+ DEBUG_ECHOLNPGM("inc OCD");
+ }
+ }
+ else { // phase 0 without OCD warning - keep on decrementing if can
+ if (OCD_TH_val) {
+ OCD_TH_val--; // try lower value
+ //DEBUG_ECHOLNPGM("LOGIC E0C - dec OCD");
+ DEBUG_ECHOLNPGM("dec OCD");
+ }
+ else {
+ test_phase = 2; // at lowest value without warning so skip phase 1
+ //DEBUG_ECHOLNPGM("LOGIC E0D - OCD at latest - go to 2");
+ DEBUG_ECHOLNPGM("OCD finalized");
+ }
+ }
+ } break;
+
+ case 1: {
+ if (status_composite & sh.STATUS_AXIS_OCD) {
+ // phase 1 with OCD warning - increment if can
+ if (OCD_TH_val >= sh.AXIS_OCD_TH_MAX) {
+ OCD_TH_val = sh.AXIS_OCD_TH_MAX; // limit to max
+ test_phase = 2; // at highest value so go to next phase
+ //DEBUG_ECHOLNPGM("LOGIC E1A - OCD at max - go to 2");
+ DEBUG_ECHOLNPGM("OCD finalized");
+ }
+ else {
+ OCD_TH_val++; // try a higher value
+ //DEBUG_ECHOLNPGM("LOGIC E1B - inc OCD");
+ DEBUG_ECHOLNPGM("inc OCD");
+ }
+ }
+ else { // phase 1 without OCD warning - normal exit to phase 2
+ test_phase = 2;
+ //DEBUG_ECHOLNPGM("LOGIC E1C - no OCD warning - go to 1");
+ DEBUG_ECHOLNPGM("OCD finalized");
+ }
+ } break;
+
+ case 2: {
+ if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474
+ test_phase = 4;
+ break;
+ }
+ if (status_composite & (sh.STATUS_AXIS_STEP_LOSS_A | sh.STATUS_AXIS_STEP_LOSS_B)) {
+ // phase 2 with stall warning - time to go to next phase
+ if (STALL_TH_val >= 127) {
+ STALL_TH_val = 127; // limit to max
+ //DEBUG_ECHOLNPGM("LOGIC E2A - STALL warning, STALL at max, quit");
+ DEBUG_ECHOLNPGM("finished - STALL at maximum value but still have stall warning");
+ test_phase = 4;
+ }
+ else {
+ test_phase = 3; // normal exit to next phase (found failing value of STALL)
+ STALL_TH_val++; // setup for first pass of phase 3
+ //DEBUG_ECHOLNPGM("LOGIC E2B - INC - STALL warning, inc Stall, go to 3");
+ DEBUG_ECHOLNPGM("inc Stall");
+ }
+ }
+ else { // phase 2 without stall warning - decrement if can
+ if (STALL_TH_val) {
+ STALL_TH_val--; // try a lower value
+ //DEBUG_ECHOLNPGM("LOGIC E2C - no STALL, dec STALL");
+ DEBUG_ECHOLNPGM("dec STALL");
+ }
+ else {
+ DEBUG_ECHOLNPGM("finished - STALL at lowest value but still do NOT have stall warning");
+ test_phase = 4;
+ //DEBUG_ECHOLNPGM("LOGIC E2D - no STALL, at lowest so quit");
+ }
+ }
+ } break;
+
+ case 3: {
+ if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474
+ test_phase = 4;
+ break;
+ }
+ if (status_composite & (sh.STATUS_AXIS_STEP_LOSS_A | sh.STATUS_AXIS_STEP_LOSS_B)) {
+ // phase 3 with stall warning - increment if can
+ if (STALL_TH_val >= 127) {
+ STALL_TH_val = 127; // limit to max
+ DEBUG_ECHOLNPGM("finished - STALL at maximum value but still have stall warning");
+ test_phase = 4;
+ //DEBUG_ECHOLNPGM("LOGIC E3A - STALL, at max so quit");
+ }
+ else {
+ STALL_TH_val++; // still looking for passing value
+ //DEBUG_ECHOLNPGM("LOGIC E3B - STALL, inc stall");
+ DEBUG_ECHOLNPGM("inc stall");
+ }
+ }
+ else { //phase 3 without stall warning but have OCD warning
+ DEBUG_ECHOLNPGM("Hardware problem - OCD warning without STALL warning");
+ test_phase = 4;
+ //DEBUG_ECHOLNPGM("LOGIC E3C - not STALLED, hardware problem (quit)");
+ }
+ } break;
+
+ }
+
+ }
+ else {
+ switch (test_phase) {
+ case 0: { // phase 0 without OCD warning - keep on decrementing if can
+ if (OCD_TH_val) {
+ OCD_TH_val--; // try lower value
+ //DEBUG_ECHOLNPGM("LOGIC N0A - DEC OCD");
+ DEBUG_ECHOLNPGM("DEC OCD");
+ }
+ else {
+ test_phase = 2; // at lowest value without warning so skip phase 1
+ //DEBUG_ECHOLNPGM("LOGIC N0B - OCD at lowest (go to phase 2)");
+ DEBUG_ECHOLNPGM("OCD finalized");
+ }
+ } break;
+
+ case 1: //DEBUG_ECHOLNPGM("LOGIC N1 (go directly to 2)"); // phase 1 without OCD warning - drop directly to phase 2
+ DEBUG_ECHOLNPGM("OCD finalized");
+
+ case 2: { // phase 2 without stall warning - keep on decrementing if can
+ if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474
+ test_phase = 4;
+ break;
+ }
+ if (STALL_TH_val) {
+ STALL_TH_val--; // try a lower value (stay in phase 2)
+ //DEBUG_ECHOLNPGM("LOGIC N2B - dec STALL");
+ DEBUG_ECHOLNPGM("dec STALL");
+ }
+ else {
+ DEBUG_ECHOLNPGM("finished - STALL at lowest value but still no stall warning");
+ test_phase = 4;
+ //DEBUG_ECHOLNPGM("LOGIC N2C - STALL at lowest (quit)");
+ }
+ } break;
+
+ case 3: {
+ if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT) { // skip all STALL_TH steps if L6474
+ test_phase = 4;
+ break;
+ }
+ test_phase = 4;
+ //DEBUG_ECHOLNPGM("LOGIC N3 - finished!");
+ DEBUG_ECHOLNPGM("finished!");
+ } break; // phase 3 without any warnings - desired exit
+ } //
+ } // end of status checks
+
+ if (test_phase != 4) {
+ for (j = 0; j < driver_count; j++) { // update threshold(s)
+ L64xxManager.set_param(axis_index[j], L6470_OCD_TH, OCD_TH_val);
+ if (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT) L64xxManager.set_param(axis_index[j], L6470_STALL_TH, STALL_TH_val);
+ if (L64xxManager.get_param(axis_index[j], L6470_OCD_TH) != OCD_TH_val) DEBUG_ECHOLNPGM("OCD mismatch");
+ if ((L64xxManager.get_param(axis_index[j], L6470_STALL_TH) != STALL_TH_val) && (sh.STATUS_AXIS_LAYOUT != L6474_STATUS_LAYOUT)) DEBUG_ECHOLNPGM("STALL mismatch");
+ }
+ }
+
+ } while (test_phase != 4);
+
+ DEBUG_ECHOLNPGM(".");
+ if (status_composite) {
+ #if ENABLED(L6470_CHITCHAT)
+ for (j = 0; j < driver_count; j++) {
+ if (j) DEBUG_ECHOPGM("...");
+ L64xxManager.error_status_decode(axis_status[j], axis_index[j],
+ sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN,
+ sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B,
+ sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT);
+ }
+ DEBUG_ECHOLNPGM(".");
+ #endif
+ DEBUG_ECHOLNPGM("Completed with errors");
+ }
+ else
+ DEBUG_ECHOLNPGM("Completed with no errors");
+ DEBUG_ECHOLNPGM(".");
+
+ L64xxManager.pause_monitor(false);
+}
+
+/**
+ * M918: increase speed until error or max feedrate achieved (as shown in configuration.h))
+ *
+ * J - select which driver(s) to monitor on multi-driver axis
+ * 0 - (default) monitor all drivers on the axis or E0
+ * 1 - monitor only X, Y, Z, E1
+ * 2 - monitor only X2, Y2, Z2, E2
+ * Xxxx, Yxxx, Zxxx, Exxx - axis to be monitored with displacement
+ * xxx (1-255) is distance moved on either side of current position
+ *
+ * I - over current threshold
+ * optional - will report current value from driver if not specified
+ *
+ * T - current (mA) setting for TVAL (0 - 4A in 31.25mA increments, rounds down) - L6474 only
+ * optional - will report current value from driver if not specified
+ *
+ * K - value for KVAL_HOLD (0 - 255) (ignored for L6474)
+ * optional - will report current value from driver if not specified
+ *
+ * M - value for microsteps (1 - 128) (optional)
+ * optional - will report current value from driver if not specified
+ */
+void GcodeSuite::M918() {
+
+ DEBUG_ECHOLNPGM("M918");
+
+ L64xxManager.pause_monitor(true); // Keep monitor_driver() from stealing status
+
+ char axis_mon[3][3] = { {" "}, {" "}, {" "} }; // list of Axes to be monitored
+ L64XX_axis_t axis_index[3];
+ uint16_t axis_status[3];
+ uint8_t driver_count = 1;
+ float position_max, position_min;
+ float final_feedrate;
+ uint8_t kval_hold;
+ uint8_t OCD_TH_val = 0;
+ uint8_t STALL_TH_val = 0;
+ uint16_t over_current_threshold;
+ constexpr uint8_t over_current_flag = true;
+
+ const L64XX_Marlin::L64XX_shadow_t &sh = L64xxManager.shadow;
+
+ uint8_t j; // general purpose counter
+
+ if (L64xxManager.get_user_input(driver_count, axis_index, axis_mon, position_max, position_min, final_feedrate, kval_hold, over_current_flag, OCD_TH_val, STALL_TH_val, over_current_threshold))
+ return; // quit if invalid user input
+
+ L64xxManager.get_status(axis_index[0]); // populate shadow array
+
+ uint8_t m_steps = parser.byteval('M');
+
+ if (m_steps != 0) {
+ LIMIT(m_steps, 1, sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT ? 16 : 128); // L6474
+
+ uint8_t stepVal;
+ for (stepVal = 0; stepVal < 8; stepVal++) { // convert to L64xx register value
+ if (m_steps == 1) break;
+ m_steps >>= 1;
+ }
+
+ if (sh.STATUS_AXIS_LAYOUT == L6474_STATUS_LAYOUT)
+ stepVal |= 0x98; // NO SYNC
+ else
+ stepVal |= (!SYNC_EN) | SYNC_SEL_1 | stepVal;
+
+ for (j = 0; j < driver_count; j++) {
+ L64xxManager.set_param(axis_index[j], dSPIN_HARD_HIZ, 0); // can't write STEP register if stepper being powered
+ // results in an extra NOOP being sent (data 00)
+ L64xxManager.set_param(axis_index[j], L6470_STEP_MODE, stepVal); // set microsteps
+ }
+ }
+ m_steps = L64xxManager.get_param(axis_index[0], L6470_STEP_MODE) & 0x07; // get microsteps
+
+ DEBUG_ECHOLNPAIR("Microsteps = ", _BV(m_steps));
+ DEBUG_ECHOLNPAIR("target (maximum) feedrate = ", final_feedrate);
+
+ const float feedrate_inc = final_feedrate / 10, // Start at 1/10 of max & go up by 1/10 per step
+ fr_limit = final_feedrate * 0.99f; // Rounding-safe comparison value
+ float current_feedrate = 0;
+
+ planner.synchronize(); // Wait for moves to complete
+
+ for (j = 0; j < driver_count; j++)
+ L64xxManager.get_status(axis_index[j]); // Clear error flags
+
+ char temp_axis_string[2] = " ";
+ temp_axis_string[0] = axis_mon[0][0]; // Need a sprintf format string
+ //temp_axis_string[1] = '\n';
+
+ char gcode_string[80];
+ uint16_t status_composite = 0;
+ DEBUG_ECHOLNPGM(".\n.\n."); // Make feedrate outputs easier to read
+
+ do {
+ current_feedrate += feedrate_inc;
+ DEBUG_ECHOLNPAIR("...feedrate = ", current_feedrate);
+
+ sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_min), uint16_t(current_feedrate));
+ gcode.process_subcommands_now_P(gcode_string);
+
+ sprintf_P(gcode_string, PSTR("G0 %s%03d F%03d"), temp_axis_string, uint16_t(position_max), uint16_t(current_feedrate));
+ gcode.process_subcommands_now_P(gcode_string);
+
+ planner.synchronize();
+
+ for (j = 0; j < driver_count; j++) {
+ axis_status[j] = (~L64xxManager.get_status(axis_index[j])) & 0x0800; // Bits of interest are all active LOW
+ status_composite |= axis_status[j];
+ }
+ if (status_composite) break; // Break on any error
+ } while (current_feedrate < fr_limit);
+
+ DEBUG_ECHOPGM("Completed with ");
+ if (status_composite) {
+ DEBUG_ECHOLNPGM("errors");
+ #if ENABLED(L6470_CHITCHAT)
+ for (j = 0; j < driver_count; j++) {
+ if (j) DEBUG_ECHOPGM("...");
+ L64xxManager.error_status_decode(axis_status[j], axis_index[j],
+ sh.STATUS_AXIS_TH_SD, sh.STATUS_AXIS_TH_WRN,
+ sh.STATUS_AXIS_STEP_LOSS_A, sh.STATUS_AXIS_STEP_LOSS_B,
+ sh.STATUS_AXIS_OCD, sh.STATUS_AXIS_LAYOUT);
+ }
+ #endif
+ }
+ else
+ DEBUG_ECHOLNPGM("no errors");
+
+ L64xxManager.pause_monitor(false);
+}
+
+#endif // HAS_L64XX
diff --git a/Marlin/src/gcode/feature/advance/M900.cpp b/Marlin/src/gcode/feature/advance/M900.cpp
new file mode 100644
index 0000000..5c7155d
--- /dev/null
+++ b/Marlin/src/gcode/feature/advance/M900.cpp
@@ -0,0 +1,147 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(LIN_ADVANCE)
+
+#include "../../gcode.h"
+#include "../../../module/planner.h"
+#include "../../../module/stepper.h"
+
+#if ENABLED(EXTRA_LIN_ADVANCE_K)
+ float other_extruder_advance_K[EXTRUDERS];
+ uint8_t lin_adv_slot = 0;
+#endif
+
+/**
+ * M900: Get or Set Linear Advance K-factor
+ * T<tool> Which tool to address
+ * K<factor> Set current advance K factor (Slot 0).
+ * L<factor> Set secondary advance K factor (Slot 1). Requires EXTRA_LIN_ADVANCE_K.
+ * S<0/1> Activate slot 0 or 1. Requires EXTRA_LIN_ADVANCE_K.
+ */
+void GcodeSuite::M900() {
+
+ auto echo_value_oor = [](const char ltr, const bool ten=true) {
+ SERIAL_CHAR('?'); SERIAL_CHAR(ltr);
+ SERIAL_ECHOPGM(" value out of range");
+ if (ten) SERIAL_ECHOPGM(" (0-10)");
+ SERIAL_ECHOLNPGM(".");
+ };
+
+ #if EXTRUDERS < 2
+ constexpr uint8_t tool_index = 0;
+ #else
+ const uint8_t tool_index = parser.intval('T', active_extruder);
+ if (tool_index >= EXTRUDERS) {
+ echo_value_oor('T', false);
+ return;
+ }
+ #endif
+
+ float &kref = planner.extruder_advance_K[tool_index], newK = kref;
+ const float oldK = newK;
+
+ #if ENABLED(EXTRA_LIN_ADVANCE_K)
+
+ float &lref = other_extruder_advance_K[tool_index];
+
+ const bool old_slot = TEST(lin_adv_slot, tool_index), // The tool's current slot (0 or 1)
+ new_slot = parser.boolval('S', old_slot); // The passed slot (default = current)
+
+ // If a new slot is being selected swap the current and
+ // saved K values. Do here so K/L will apply correctly.
+ if (new_slot != old_slot) { // Not the same slot?
+ SET_BIT_TO(lin_adv_slot, tool_index, new_slot); // Update the slot for the tool
+ newK = lref; // Get new K value from backup
+ lref = oldK; // Save K to backup
+ }
+
+ // Set the main K value. Apply if the main slot is active.
+ if (parser.seenval('K')) {
+ const float K = parser.value_float();
+ if (!WITHIN(K, 0, 10)) echo_value_oor('K');
+ else if (new_slot) lref = K; // S1 Knn
+ else newK = K; // S0 Knn
+ }
+
+ // Set the extra K value. Apply if the extra slot is active.
+ if (parser.seenval('L')) {
+ const float L = parser.value_float();
+ if (!WITHIN(L, 0, 10)) echo_value_oor('L');
+ else if (!new_slot) lref = L; // S0 Lnn
+ else newK = L; // S1 Lnn
+ }
+
+ #else
+
+ if (parser.seenval('K')) {
+ const float K = parser.value_float();
+ if (WITHIN(K, 0, 10))
+ newK = K;
+ else
+ echo_value_oor('K');
+ }
+
+ #endif
+
+ if (newK != oldK) {
+ planner.synchronize();
+ kref = newK;
+ }
+
+ if (!parser.seen_any()) {
+
+ #if ENABLED(EXTRA_LIN_ADVANCE_K)
+
+ #if EXTRUDERS < 2
+ SERIAL_ECHOLNPAIR("Advance S", int(new_slot), " K", kref, "(S", int(!new_slot), " K", lref, ")");
+ #else
+ LOOP_L_N(i, EXTRUDERS) {
+ const bool slot = TEST(lin_adv_slot, i);
+ SERIAL_ECHOLNPAIR("Advance T", int(i), " S", int(slot), " K", planner.extruder_advance_K[i],
+ "(S", int(!slot), " K", other_extruder_advance_K[i], ")");
+ SERIAL_EOL();
+ }
+ #endif
+
+ #else
+
+ SERIAL_ECHO_START();
+ #if EXTRUDERS < 2
+ SERIAL_ECHOLNPAIR("Advance K=", planner.extruder_advance_K[0]);
+ #else
+ SERIAL_ECHOPGM("Advance K");
+ LOOP_L_N(i, EXTRUDERS) {
+ SERIAL_CHAR(' ', '0' + i, ':');
+ SERIAL_DECIMAL(planner.extruder_advance_K[i]);
+ }
+ SERIAL_EOL();
+ #endif
+
+ #endif
+ }
+
+}
+
+#endif // LIN_ADVANCE
diff --git a/Marlin/src/gcode/feature/baricuda/M126-M129.cpp b/Marlin/src/gcode/feature/baricuda/M126-M129.cpp
new file mode 100644
index 0000000..edeba0d
--- /dev/null
+++ b/Marlin/src/gcode/feature/baricuda/M126-M129.cpp
@@ -0,0 +1,58 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(BARICUDA)
+
+#include "../../gcode.h"
+#include "../../../feature/baricuda.h"
+
+#if HAS_HEATER_1
+
+ /**
+ * M126: Heater 1 valve open
+ */
+ void GcodeSuite::M126() { baricuda_valve_pressure = parser.byteval('S', 255); }
+
+ /**
+ * M127: Heater 1 valve close
+ */
+ void GcodeSuite::M127() { baricuda_valve_pressure = 0; }
+
+#endif // HAS_HEATER_1
+
+#if HAS_HEATER_2
+
+ /**
+ * M128: Heater 2 valve open
+ */
+ void GcodeSuite::M128() { baricuda_e_to_p_pressure = parser.byteval('S', 255); }
+
+ /**
+ * M129: Heater 2 valve close
+ */
+ void GcodeSuite::M129() { baricuda_e_to_p_pressure = 0; }
+
+#endif // HAS_HEATER_2
+
+#endif // BARICUDA
diff --git a/Marlin/src/gcode/feature/camera/M240.cpp b/Marlin/src/gcode/feature/camera/M240.cpp
new file mode 100644
index 0000000..fc350d8
--- /dev/null
+++ b/Marlin/src/gcode/feature/camera/M240.cpp
@@ -0,0 +1,204 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(PHOTO_GCODE)
+
+#include "../../gcode.h"
+#include "../../../module/motion.h" // for active_extruder and current_position
+
+#if PIN_EXISTS(CHDK)
+ millis_t chdk_timeout; // = 0
+#endif
+
+#if defined(PHOTO_POSITION) && PHOTO_DELAY_MS > 0
+ #include "../../../MarlinCore.h" // for idle()
+#endif
+
+#ifdef PHOTO_RETRACT_MM
+
+ #define _PHOTO_RETRACT_MM (PHOTO_RETRACT_MM + 0)
+
+ #include "../../../module/planner.h"
+ #include "../../../module/temperature.h"
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ #include "../../../feature/pause.h"
+ #endif
+
+ #ifdef PHOTO_RETRACT_MM
+ inline void e_move_m240(const float length, const feedRate_t &fr_mm_s) {
+ if (length && thermalManager.hotEnoughToExtrude(active_extruder))
+ unscaled_e_move(length, fr_mm_s);
+ }
+ #endif
+
+#endif
+
+#if PIN_EXISTS(PHOTOGRAPH)
+
+ FORCE_INLINE void set_photo_pin(const uint8_t state) {
+ constexpr uint32_t pulse_length = (
+ #ifdef PHOTO_PULSES_US
+ PHOTO_PULSE_DELAY_US
+ #else
+ 15 // 15.24 from _delay_ms(0.01524)
+ #endif
+ );
+ WRITE(PHOTOGRAPH_PIN, state);
+ delayMicroseconds(pulse_length);
+ }
+
+ FORCE_INLINE void tweak_photo_pin() { set_photo_pin(HIGH); set_photo_pin(LOW); }
+
+ #ifdef PHOTO_PULSES_US
+
+ inline void pulse_photo_pin(const uint32_t duration, const uint8_t state) {
+ if (state) {
+ for (const uint32_t stop = micros() + duration; micros() < stop;)
+ tweak_photo_pin();
+ }
+ else
+ delayMicroseconds(duration);
+ }
+
+ inline void spin_photo_pin() {
+ static constexpr uint32_t sequence[] = PHOTO_PULSES_US;
+ LOOP_L_N(i, COUNT(sequence))
+ pulse_photo_pin(sequence[i], !(i & 1));
+ }
+
+ #else
+
+ constexpr uint8_t NUM_PULSES = 16;
+ inline void spin_photo_pin() { for (uint8_t i = NUM_PULSES; i--;) tweak_photo_pin(); }
+
+ #endif
+#endif
+
+/**
+ * M240: Trigger a camera by...
+ *
+ * - CHDK : Emulate a Canon RC-1 with a configurable ON duration.
+ * https://captain-slow.dk/2014/03/09/3d-printing-timelapses/
+ * - PHOTOGRAPH_PIN : Pulse a digital pin 16 times.
+ * See https://www.doc-diy.net/photo/rc-1_hacked/
+ * - PHOTO_SWITCH_POSITION : Bump a physical switch with the X-carriage using a
+ * configured position, delay, and retract length.
+ *
+ * PHOTO_POSITION parameters:
+ * A - X offset to the return position
+ * B - Y offset to the return position
+ * F - Override the XY movement feedrate
+ * R - Retract/recover length (current units)
+ * S - Retract/recover feedrate (mm/m)
+ * X - Move to X before triggering the shutter
+ * Y - Move to Y before triggering the shutter
+ * Z - Raise Z by a distance before triggering the shutter
+ *
+ * PHOTO_SWITCH_POSITION parameters:
+ * D - Duration (ms) to hold down switch (Requires PHOTO_SWITCH_MS)
+ * P - Delay (ms) after triggering the shutter (Requires PHOTO_SWITCH_MS)
+ * I - Switch trigger position override X
+ * J - Switch trigger position override Y
+ */
+void GcodeSuite::M240() {
+
+ #ifdef PHOTO_POSITION
+
+ if (homing_needed_error()) return;
+
+ const xyz_pos_t old_pos = {
+ current_position.x + parser.linearval('A'),
+ current_position.y + parser.linearval('B'),
+ current_position.z
+ };
+
+ #ifdef PHOTO_RETRACT_MM
+ const float rval = parser.seenval('R') ? parser.value_linear_units() : _PHOTO_RETRACT_MM;
+ feedRate_t sval = (
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ PAUSE_PARK_RETRACT_FEEDRATE
+ #elif ENABLED(FWRETRACT)
+ RETRACT_FEEDRATE
+ #else
+ 45
+ #endif
+ );
+ if (parser.seenval('S')) sval = parser.value_feedrate();
+ e_move_m240(-rval, sval);
+ #endif
+
+ feedRate_t fr_mm_s = MMM_TO_MMS(parser.linearval('F'));
+ if (fr_mm_s) NOLESS(fr_mm_s, 10.0f);
+
+ constexpr xyz_pos_t photo_position = PHOTO_POSITION;
+ xyz_pos_t raw = {
+ parser.seenval('X') ? RAW_X_POSITION(parser.value_linear_units()) : photo_position.x,
+ parser.seenval('Y') ? RAW_Y_POSITION(parser.value_linear_units()) : photo_position.y,
+ (parser.seenval('Z') ? parser.value_linear_units() : photo_position.z) + current_position.z
+ };
+ apply_motion_limits(raw);
+ do_blocking_move_to(raw, fr_mm_s);
+
+ #ifdef PHOTO_SWITCH_POSITION
+ constexpr xy_pos_t photo_switch_position = PHOTO_SWITCH_POSITION;
+ const xy_pos_t sraw = {
+ parser.seenval('I') ? RAW_X_POSITION(parser.value_linear_units()) : photo_switch_position.x,
+ parser.seenval('J') ? RAW_Y_POSITION(parser.value_linear_units()) : photo_switch_position.y
+ };
+ do_blocking_move_to_xy(sraw, get_homing_bump_feedrate(X_AXIS));
+ #if PHOTO_SWITCH_MS > 0
+ safe_delay(parser.intval('D', PHOTO_SWITCH_MS));
+ #endif
+ do_blocking_move_to(raw);
+ #endif
+
+ #endif
+
+ #if PIN_EXISTS(CHDK)
+
+ OUT_WRITE(CHDK_PIN, HIGH);
+ chdk_timeout = millis() + parser.intval('D', PHOTO_SWITCH_MS);
+
+ #elif HAS_PHOTOGRAPH
+
+ spin_photo_pin();
+ delay(7.33);
+ spin_photo_pin();
+
+ #endif
+
+ #ifdef PHOTO_POSITION
+ #if PHOTO_DELAY_MS > 0
+ const millis_t timeout = millis() + parser.intval('P', PHOTO_DELAY_MS);
+ while (PENDING(millis(), timeout)) idle();
+ #endif
+ do_blocking_move_to(old_pos, fr_mm_s);
+ #ifdef PHOTO_RETRACT_MM
+ e_move_m240(rval, sval);
+ #endif
+ #endif
+}
+
+#endif // PHOTO_GCODE
diff --git a/Marlin/src/gcode/feature/cancel/M486.cpp b/Marlin/src/gcode/feature/cancel/M486.cpp
new file mode 100644
index 0000000..1f14ae0
--- /dev/null
+++ b/Marlin/src/gcode/feature/cancel/M486.cpp
@@ -0,0 +1,57 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(CANCEL_OBJECTS)
+
+#include "../../gcode.h"
+#include "../../../feature/cancel_object.h"
+
+/**
+ * M486: A simple interface to cancel objects
+ *
+ * T[count] : Reset objects and/or set the count
+ * S<index> : Start an object with the given index
+ * P<index> : Cancel the object with the given index
+ * U<index> : Un-cancel object with the given index
+ * C : Cancel the current object (the last index given by S<index>)
+ * S-1 : Start a non-object like a brim or purge tower that should always print
+ */
+void GcodeSuite::M486() {
+
+ if (parser.seen('T')) {
+ cancelable.reset();
+ cancelable.object_count = parser.intval('T', 1);
+ }
+
+ if (parser.seen('S'))
+ cancelable.set_active_object(parser.value_int());
+
+ if (parser.seen('C')) cancelable.cancel_active_object();
+
+ if (parser.seen('P')) cancelable.cancel_object(parser.value_int());
+
+ if (parser.seen('U')) cancelable.uncancel_object(parser.value_int());
+}
+
+#endif // CANCEL_OBJECTS
diff --git a/Marlin/src/gcode/feature/caselight/M355.cpp b/Marlin/src/gcode/feature/caselight/M355.cpp
new file mode 100644
index 0000000..12ae5cf
--- /dev/null
+++ b/Marlin/src/gcode/feature/caselight/M355.cpp
@@ -0,0 +1,73 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(CASE_LIGHT_ENABLE)
+
+#include "../../../feature/caselight.h"
+#include "../../gcode.h"
+
+/**
+ * M355: Turn case light on/off and set brightness
+ *
+ * P<byte> Set case light brightness (PWM pin required - ignored otherwise)
+ *
+ * S<bool> 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)
+ */
+void GcodeSuite::M355() {
+ bool didset = false;
+ #if CASELIGHT_USES_BRIGHTNESS
+ if (parser.seenval('P')) {
+ didset = true;
+ caselight.brightness = parser.value_byte();
+ }
+ #endif
+ const bool sflag = parser.seenval('S');
+ if (sflag) {
+ didset = true;
+ caselight.on = parser.value_bool();
+ }
+ if (didset) caselight.update(sflag);
+
+ // Always report case light status
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("Case light: ");
+ if (!caselight.on)
+ SERIAL_ECHOLNPGM(STR_OFF);
+ else {
+ #if CASELIGHT_USES_BRIGHTNESS
+ if (PWM_PIN(CASE_LIGHT_PIN)) {
+ SERIAL_ECHOLN(int(caselight.brightness));
+ return;
+ }
+ #endif
+ SERIAL_ECHOLNPGM(STR_ON);
+ }
+}
+
+#endif // CASE_LIGHT_ENABLE
diff --git a/Marlin/src/gcode/feature/clean/G12.cpp b/Marlin/src/gcode/feature/clean/G12.cpp
new file mode 100644
index 0000000..216db5b
--- /dev/null
+++ b/Marlin/src/gcode/feature/clean/G12.cpp
@@ -0,0 +1,80 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(NOZZLE_CLEAN_FEATURE)
+
+#include "../../../libs/nozzle.h"
+
+#include "../../gcode.h"
+#include "../../parser.h"
+#include "../../../module/motion.h"
+
+#if HAS_LEVELING
+ #include "../../../module/planner.h"
+ #include "../../../feature/bedlevel/bedlevel.h"
+#endif
+
+/**
+ * G12: Clean the nozzle
+ *
+ * E<bool> : 0=Never or 1=Always apply the "software endstop" limits
+ * P0 S<strokes> : Stroke cleaning with S strokes
+ * P1 Sn T<objects> : Zigzag cleaning with S repeats and T zigzags
+ * P2 Sn R<radius> : Circle cleaning with S repeats and R radius
+ */
+void GcodeSuite::G12() {
+ // Don't allow nozzle cleaning without homing first
+ if (homing_needed_error()) return;
+
+ #ifdef WIPE_SEQUENCE_COMMANDS
+ if (!parser.seen_any()) {
+ gcode.process_subcommands_now_P(PSTR(WIPE_SEQUENCE_COMMANDS));
+ return;
+ }
+ #endif
+
+ 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.linearval('R', NOZZLE_CLEAN_CIRCLE_RADIUS);
+
+ const bool seenxyz = parser.seen("XYZ");
+ const uint8_t cleans = (!seenxyz || parser.boolval('X') ? _BV(X_AXIS) : 0)
+ | (!seenxyz || parser.boolval('Y') ? _BV(Y_AXIS) : 0)
+ | TERN(NOZZLE_CLEAN_NO_Z, 0, (!seenxyz || parser.boolval('Z') ? _BV(Z_AXIS) : 0))
+ ;
+
+ #if HAS_LEVELING
+ // Disable bed leveling if cleaning Z
+ TEMPORARY_BED_LEVELING_STATE(!TEST(cleans, Z_AXIS) && planner.leveling_active);
+ #endif
+
+ SET_SOFT_ENDSTOP_LOOSE(!parser.boolval('E'));
+
+ nozzle.clean(pattern, strokes, radius, objects, cleans);
+
+ SET_SOFT_ENDSTOP_LOOSE(false);
+}
+
+#endif // NOZZLE_CLEAN_FEATURE
diff --git a/Marlin/src/gcode/feature/controllerfan/M710.cpp b/Marlin/src/gcode/feature/controllerfan/M710.cpp
new file mode 100644
index 0000000..cc45073
--- /dev/null
+++ b/Marlin/src/gcode/feature/controllerfan/M710.cpp
@@ -0,0 +1,81 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(CONTROLLER_FAN_EDITABLE)
+
+#include "../../gcode.h"
+#include "../../../feature/controllerfan.h"
+
+void M710_report(const bool forReplay) {
+ if (!forReplay) { SERIAL_ECHOLNPGM("; Controller Fan"); SERIAL_ECHO_START(); }
+ SERIAL_ECHOLNPAIR(" M710"
+ " S", int(controllerFan.settings.active_speed),
+ " I", int(controllerFan.settings.idle_speed),
+ " A", int(controllerFan.settings.auto_mode),
+ " D", controllerFan.settings.duration,
+ " ; (", (int(controllerFan.settings.active_speed) * 100) / 255, "%"
+ " ", (int(controllerFan.settings.idle_speed) * 100) / 255, "%)"
+ );
+}
+
+/**
+ * M710: Set controller fan settings
+ *
+ * R : Reset to defaults
+ * S[0-255] : Fan speed when motors are active
+ * I[0-255] : Fan speed when motors are idle
+ * A[0|1] : Turn auto mode on or off
+ * D : Set auto mode idle duration
+ *
+ * Examples:
+ * M710 ; Report current Settings
+ * M710 R ; Reset SIAD to defaults
+ * M710 I64 ; Set controller fan Idle Speed to 25%
+ * M710 S255 ; Set controller fan Active Speed to 100%
+ * M710 S0 ; Set controller fan Active Speed to OFF
+ * M710 I255 A0 ; Set controller fan Idle Speed to 100% with Auto Mode OFF
+ * M710 I127 A1 S255 D160 ; Set controller fan idle speed 50%, AutoMode On, Fan speed 100%, duration to 160 Secs
+ */
+void GcodeSuite::M710() {
+
+ const bool seenR = parser.seen('R');
+ if (seenR) controllerFan.reset();
+
+ const bool seenS = parser.seenval('S');
+ if (seenS) controllerFan.settings.active_speed = parser.value_byte();
+
+ const bool seenI = parser.seenval('I');
+ if (seenI) controllerFan.settings.idle_speed = parser.value_byte();
+
+ const bool seenA = parser.seenval('A');
+ if (seenA) controllerFan.settings.auto_mode = parser.value_bool();
+
+ const bool seenD = parser.seenval('D');
+ if (seenD) controllerFan.settings.duration = parser.value_ushort();
+
+ if (!(seenR || seenS || seenI || seenA || seenD))
+ M710_report(false);
+}
+
+#endif // CONTROLLER_FAN_EDITABLE
diff --git a/Marlin/src/gcode/feature/digipot/M907-M910.cpp b/Marlin/src/gcode/feature/digipot/M907-M910.cpp
new file mode 100644
index 0000000..e463666
--- /dev/null
+++ b/Marlin/src/gcode/feature/digipot/M907-M910.cpp
@@ -0,0 +1,102 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ANY(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_PWM, HAS_MOTOR_CURRENT_I2C, HAS_MOTOR_CURRENT_DAC)
+
+#include "../../gcode.h"
+
+#if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM
+ #include "../../../module/stepper.h"
+#endif
+
+#if HAS_MOTOR_CURRENT_I2C
+ #include "../../../feature/digipot/digipot.h"
+#endif
+
+#if ENABLED(HAS_MOTOR_CURRENT_DAC)
+ #include "../../../feature/dac/stepper_dac.h"
+#endif
+
+/**
+ * M907: Set digital trimpot motor current using axis codes X, Y, Z, E, B, S
+ */
+void GcodeSuite::M907() {
+ #if HAS_MOTOR_CURRENT_SPI
+
+ LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.set_digipot_current(i, parser.value_int());
+ if (parser.seenval('B')) stepper.set_digipot_current(4, parser.value_int());
+ if (parser.seenval('S')) LOOP_LE_N(i, 4) stepper.set_digipot_current(i, parser.value_int());
+
+ #elif HAS_MOTOR_CURRENT_PWM
+
+ #if ANY_PIN(MOTOR_CURRENT_PWM_X, MOTOR_CURRENT_PWM_Y, MOTOR_CURRENT_PWM_XY)
+ if (parser.seenval('X') || parser.seenval('Y')) stepper.set_digipot_current(0, parser.value_int());
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z)
+ if (parser.seenval('Z')) stepper.set_digipot_current(1, parser.value_int());
+ #endif
+ #if PIN_EXISTS(MOTOR_CURRENT_PWM_E)
+ if (parser.seenval('E')) stepper.set_digipot_current(2, parser.value_int());
+ #endif
+
+ #endif
+
+ #if HAS_MOTOR_CURRENT_I2C
+ // this one uses actual amps in floating point
+ LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) digipot_i2c.set_current(i, parser.value_float());
+ // Additional extruders use B,C,D for channels 4,5,6.
+ // TODO: Change these parameters because 'E' is used. B<index>?
+ for (uint8_t i = E_AXIS + 1; i < DIGIPOT_I2C_NUM_CHANNELS; i++)
+ if (parser.seenval('B' + i - (E_AXIS + 1))) digipot_i2c.set_current(i, parser.value_float());
+ #endif
+
+ #if ENABLED(HAS_MOTOR_CURRENT_DAC)
+ if (parser.seenval('S')) {
+ const float dac_percent = parser.value_float();
+ LOOP_LE_N(i, 4) stepper_dac.set_current_percent(i, dac_percent);
+ }
+ LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper_dac.set_current_percent(i, parser.value_float());
+ #endif
+}
+
+#if EITHER(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_DAC)
+
+ /**
+ * M908: Control digital trimpot directly (M908 P<pin> S<current>)
+ */
+ void GcodeSuite::M908() {
+ TERN_(HAS_MOTOR_CURRENT_SPI, stepper.set_digipot_value_spi(parser.intval('P'), parser.intval('S')));
+ TERN_(HAS_MOTOR_CURRENT_DAC, stepper_dac.set_current_value(parser.byteval('P', -1), parser.ushortval('S', 0)));
+ }
+
+ #if ENABLED(HAS_MOTOR_CURRENT_DAC)
+
+ void GcodeSuite::M909() { stepper_dac.print_values(); }
+ void GcodeSuite::M910() { stepper_dac.commit_eeprom(); }
+
+ #endif // HAS_MOTOR_CURRENT_DAC
+
+#endif // HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_DAC
+
+#endif // HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM || HAS_MOTOR_CURRENT_I2C || HAS_MOTOR_CURRENT_DAC
diff --git a/Marlin/src/gcode/feature/filwidth/M404-M407.cpp b/Marlin/src/gcode/feature/filwidth/M404-M407.cpp
new file mode 100644
index 0000000..a70f7a6
--- /dev/null
+++ b/Marlin/src/gcode/feature/filwidth/M404-M407.cpp
@@ -0,0 +1,71 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(FILAMENT_WIDTH_SENSOR)
+
+#include "../../../feature/filwidth.h"
+#include "../../../module/planner.h"
+#include "../../../MarlinCore.h"
+#include "../../gcode.h"
+
+/**
+ * M404: Display or set (in current units) the nominal filament width (3mm, 1.75mm ) W<3.0>
+ */
+void GcodeSuite::M404() {
+ if (parser.seenval('W')) {
+ filwidth.nominal_mm = parser.value_linear_units();
+ planner.volumetric_area_nominal = CIRCLE_AREA(filwidth.nominal_mm * 0.5);
+ }
+ else
+ SERIAL_ECHOLNPAIR("Filament dia (nominal mm):", filwidth.nominal_mm);
+}
+
+/**
+ * M405: Turn on filament sensor for control
+ */
+void GcodeSuite::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.seenval('D'))
+ filwidth.set_delay_cm(parser.value_byte());
+
+ filwidth.enable(true);
+}
+
+/**
+ * M406: Turn off filament sensor for control
+ */
+void GcodeSuite::M406() {
+ filwidth.enable(false);
+ planner.calculate_volumetric_multipliers(); // Restore correct 'volumetric_multiplier' value
+}
+
+/**
+ * M407: Get measured filament diameter on serial output
+ */
+void GcodeSuite::M407() {
+ SERIAL_ECHOLNPAIR("Filament dia (measured mm):", filwidth.measured_mm);
+}
+
+#endif // FILAMENT_WIDTH_SENSOR
diff --git a/Marlin/src/gcode/feature/fwretract/G10_G11.cpp b/Marlin/src/gcode/feature/fwretract/G10_G11.cpp
new file mode 100644
index 0000000..219502f
--- /dev/null
+++ b/Marlin/src/gcode/feature/fwretract/G10_G11.cpp
@@ -0,0 +1,51 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(FWRETRACT)
+
+#include "../../../feature/fwretract.h"
+#include "../../gcode.h"
+#include "../../../module/motion.h"
+
+/**
+ * G10 - Retract filament according to settings of M207
+ * TODO: Handle 'G10 P' for tool settings and 'G10 L' for workspace settings
+ */
+void GcodeSuite::G10() {
+ #if HAS_MULTI_EXTRUDER
+ const bool rs = parser.boolval('S');
+ #endif
+ fwretract.retract(true
+ #if HAS_MULTI_EXTRUDER
+ , rs
+ #endif
+ );
+}
+
+/**
+ * G11 - Recover filament according to settings of M208
+ */
+void GcodeSuite::G11() { fwretract.retract(false); }
+
+#endif // FWRETRACT
diff --git a/Marlin/src/gcode/feature/fwretract/M207-M209.cpp b/Marlin/src/gcode/feature/fwretract/M207-M209.cpp
new file mode 100644
index 0000000..538f16c
--- /dev/null
+++ b/Marlin/src/gcode/feature/fwretract/M207-M209.cpp
@@ -0,0 +1,74 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(FWRETRACT)
+
+#include "../../../feature/fwretract.h"
+#include "../../gcode.h"
+
+/**
+ * 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_zraise
+ */
+void GcodeSuite::M207() {
+ if (parser.seen('S')) fwretract.settings.retract_length = parser.value_axis_units(E_AXIS);
+ if (parser.seen('F')) fwretract.settings.retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
+ if (parser.seen('Z')) fwretract.settings.retract_zraise = parser.value_linear_units();
+ if (parser.seen('W')) fwretract.settings.swap_retract_length = parser.value_axis_units(E_AXIS);
+}
+
+/**
+ * M208: Set firmware un-retraction values
+ *
+ * S[+units] retract_recover_extra (in addition to M207 S*)
+ * W[+units] swap_retract_recover_extra (multi-extruder)
+ * F[units/min] retract_recover_feedrate_mm_s
+ * R[units/min] swap_retract_recover_feedrate_mm_s
+ */
+void GcodeSuite::M208() {
+ if (parser.seen('S')) fwretract.settings.retract_recover_extra = parser.value_axis_units(E_AXIS);
+ if (parser.seen('F')) fwretract.settings.retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
+ if (parser.seen('R')) fwretract.settings.swap_retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
+ if (parser.seen('W')) fwretract.settings.swap_retract_recover_extra = parser.value_axis_units(E_AXIS);
+}
+
+#if ENABLED(FWRETRACT_AUTORETRACT)
+
+ /**
+ * M209: Enable automatic retract (M209 S1)
+ * For slicers that don't support G10/11, reversed extrude-only
+ * moves will be classified as retraction.
+ */
+ void GcodeSuite::M209() {
+ if (MIN_AUTORETRACT <= MAX_AUTORETRACT && parser.seen('S'))
+ fwretract.enable_autoretract(parser.value_bool());
+ }
+
+#endif // FWRETRACT_AUTORETRACT
+
+#endif // FWRETRACT
diff --git a/Marlin/src/gcode/feature/i2c/M260_M261.cpp b/Marlin/src/gcode/feature/i2c/M260_M261.cpp
new file mode 100644
index 0000000..438d152
--- /dev/null
+++ b/Marlin/src/gcode/feature/i2c/M260_M261.cpp
@@ -0,0 +1,76 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(EXPERIMENTAL_I2CBUS)
+
+#include "../../gcode.h"
+
+#include "../../../feature/twibus.h"
+
+/**
+ * M260: Send data to a I2C slave device
+ *
+ * This is a PoC, the formatting and arguments for the GCODE will
+ * change to be more compatible, the current proposal is:
+ *
+ * M260 A<slave device address base 10> ; Sets the I2C slave address the data will be sent to
+ *
+ * M260 B<byte-1 value in base 10>
+ * M260 B<byte-2 value in base 10>
+ * M260 B<byte-3 value in base 10>
+ *
+ * M260 S1 ; Send the buffered data and reset the buffer
+ * M260 R1 ; Reset the buffer without sending data
+ */
+void GcodeSuite::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<slave device address base 10> B<number of bytes>
+ */
+void GcodeSuite::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_MSG("Bad i2c request");
+}
+
+#endif
diff --git a/Marlin/src/gcode/feature/leds/M150.cpp b/Marlin/src/gcode/feature/leds/M150.cpp
new file mode 100644
index 0000000..cf09bf1
--- /dev/null
+++ b/Marlin/src/gcode/feature/leds/M150.cpp
@@ -0,0 +1,84 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_COLOR_LEDS
+
+#include "../../gcode.h"
+#include "../../../feature/leds/leds.h"
+
+/**
+ * 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
+ *
+ * With NEOPIXEL_LED:
+ * I<index> Set the NeoPixel index to affect. Default: All
+ *
+ * With NEOPIXEL2_SEPARATE:
+ * S<index> The NeoPixel strip to set. Default is index 0.
+ *
+ * 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
+ * M150 I1 R ; Set NEOPIXEL index 1 to red
+ * M150 S1 I1 R ; Set SEPARATE index 1 to red
+ */
+
+void GcodeSuite::M150() {
+ #if ENABLED(NEOPIXEL_LED)
+ const uint8_t index = parser.intval('I', -1);
+ #if ENABLED(NEOPIXEL2_SEPARATE)
+ const uint8_t unit = parser.intval('S'),
+ brightness = unit ? neo2.brightness() : neo.brightness();
+ *(unit ? &neo2.neoindex : &neo.neoindex) = index;
+ #else
+ const uint8_t brightness = neo.brightness();
+ neo.neoindex = index;
+ #endif
+ #endif
+
+ const LEDColor 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) : brightness
+ );
+
+ #if ENABLED(NEOPIXEL2_SEPARATE)
+ if (unit == 1) { leds2.set_color(color); return; }
+ #endif
+
+ leds.set_color(color);
+}
+
+#endif // HAS_COLOR_LEDS
diff --git a/Marlin/src/gcode/feature/leds/M7219.cpp b/Marlin/src/gcode/feature/leds/M7219.cpp
new file mode 100644
index 0000000..a6ee711
--- /dev/null
+++ b/Marlin/src/gcode/feature/leds/M7219.cpp
@@ -0,0 +1,93 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(MAX7219_GCODE)
+
+#include "../../gcode.h"
+#include "../../../feature/max7219.h"
+
+/**
+ * M7219: Control the Max7219 LED matrix
+ *
+ * I - Initialize (clear) the matrix
+ * F - Fill the matrix (set all bits)
+ * P - Dump the led_line[] array values
+ * C<column> - Set a column to the bitmask given by 'V' (Units 0-3 in portrait layout)
+ * R<row> - Set a row to the bitmask given by 'V' (Units 0-3 in landscape layout)
+ * X<pos> - X index of an LED to set or toggle
+ * Y<pos> - Y index of an LED to set or toggle
+ * V<value> - LED on/off state or row/column bitmask (8, 16, 24, or 32-bits)
+ * ('C' / 'R' can be used to update up to 4 units at once)
+ *
+ * Directly set a native matrix row to the 8-bit value 'V':
+ * D<line> - Display line (0..7)
+ * U<unit> - Unit index (0..MAX7219_NUMBER_UNITS-1)
+ */
+void GcodeSuite::M7219() {
+ if (parser.seen('I')) {
+ max7219.register_setup();
+ max7219.clear();
+ }
+
+ if (parser.seen('F')) max7219.fill();
+
+ const uint32_t v = parser.ulongval('V');
+
+ if (parser.seenval('R')) {
+ const uint8_t r = parser.value_byte();
+ max7219.set_row(r, v);
+ }
+ else if (parser.seenval('C')) {
+ const uint8_t c = parser.value_byte();
+ max7219.set_column(c, v);
+ }
+ else if (parser.seenval('X') || parser.seenval('Y')) {
+ const uint8_t x = parser.byteval('X'), y = parser.byteval('Y');
+ if (parser.seenval('V'))
+ max7219.led_set(x, y, v > 0);
+ else
+ max7219.led_toggle(x, y);
+ }
+ else if (parser.seen('D')) {
+ const uint8_t uline = parser.value_byte() & 0x7,
+ line = uline + (parser.byteval('U') << 3);
+ if (line < MAX7219_LINES) {
+ max7219.led_line[line] = v;
+ return max7219.refresh_line(line);
+ }
+ }
+
+ if (parser.seen('P')) {
+ LOOP_L_N(r, MAX7219_LINES) {
+ SERIAL_ECHOPGM("led_line[");
+ if (r < 10) SERIAL_CHAR(' ');
+ SERIAL_ECHO(int(r));
+ SERIAL_ECHOPGM("]=");
+ for (uint8_t b = 8; b--;) SERIAL_CHAR('0' + TEST(max7219.led_line[r], b));
+ SERIAL_EOL();
+ }
+ }
+}
+
+#endif // MAX7219_GCODE
diff --git a/Marlin/src/gcode/feature/macro/M810-M819.cpp b/Marlin/src/gcode/feature/macro/M810-M819.cpp
new file mode 100644
index 0000000..7b9e1a1
--- /dev/null
+++ b/Marlin/src/gcode/feature/macro/M810-M819.cpp
@@ -0,0 +1,65 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(GCODE_MACROS)
+
+#include "../../gcode.h"
+#include "../../queue.h"
+#include "../../parser.h"
+
+char gcode_macros[GCODE_MACROS_SLOTS][GCODE_MACROS_SLOT_SIZE + 1] = {{ 0 }};
+
+/**
+ * M810_819: Set/execute a G-code macro.
+ *
+ * Usage:
+ * M810 <command>|... Set Macro 0 to the given commands, separated by the pipe character
+ * M810 Execute Macro 0
+ */
+void GcodeSuite::M810_819() {
+ const uint8_t index = parser.codenum - 810;
+ if (index >= GCODE_MACROS_SLOTS) return;
+
+ const size_t len = strlen(parser.string_arg);
+
+ if (len) {
+ // Set a macro
+ if (len > GCODE_MACROS_SLOT_SIZE)
+ SERIAL_ERROR_MSG("Macro too long.");
+ else {
+ char c, *s = parser.string_arg, *d = gcode_macros[index];
+ do {
+ c = *s++;
+ *d++ = c == '|' ? '\n' : c;
+ } while (c);
+ }
+ }
+ else {
+ // Execute a macro
+ char * const cmd = gcode_macros[index];
+ if (strlen(cmd)) process_subcommands_now(cmd);
+ }
+}
+
+#endif // GCODE_MACROS
diff --git a/Marlin/src/gcode/feature/mixing/M163-M165.cpp b/Marlin/src/gcode/feature/mixing/M163-M165.cpp
new file mode 100644
index 0000000..a4cb64e
--- /dev/null
+++ b/Marlin/src/gcode/feature/mixing/M163-M165.cpp
@@ -0,0 +1,101 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(MIXING_EXTRUDER)
+
+#include "../../gcode.h"
+#include "../../../feature/mixing.h"
+
+/**
+ * M163: Set a single mix factor for a mixing extruder
+ * This is called "weight" by some systems.
+ * Must be followed by M164 to normalize and commit them.
+ *
+ * S[index] The channel index to set
+ * P[float] The mix value
+ */
+void GcodeSuite::M163() {
+ const int mix_index = parser.intval('S');
+ if (mix_index < MIXING_STEPPERS)
+ mixer.set_collector(mix_index, parser.floatval('P'));
+}
+
+/**
+ * M164: Normalize and commit the mix.
+ *
+ * S[index] The virtual tool to store
+ * If 'S' is omitted update the active virtual tool.
+ */
+void GcodeSuite::M164() {
+ #if MIXING_VIRTUAL_TOOLS > 1
+ const int tool_index = parser.intval('S', -1);
+ #else
+ constexpr int tool_index = 0;
+ #endif
+ if (tool_index >= 0) {
+ if (tool_index < MIXING_VIRTUAL_TOOLS)
+ mixer.normalize(tool_index);
+ }
+ else
+ mixer.normalize();
+}
+
+#if ENABLED(DIRECT_MIXING_IN_G1)
+
+ /**
+ * M165: Set multiple mix factors for a mixing extruder.
+ * Omitted factors will be set to 0.
+ * The mix is normalized and stored in the current virtual tool.
+ *
+ * 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
+ */
+ void GcodeSuite::M165() {
+ // 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
+ const char mixing_codes[] = { LIST_N(MIXING_STEPPERS, 'A', 'B', 'C', 'D', 'H', 'I') };
+ uint8_t mix_bits = 0;
+ MIXER_STEPPER_LOOP(i) {
+ if (parser.seenval(mixing_codes[i])) {
+ SBI(mix_bits, i);
+ mixer.set_collector(i, parser.value_float());
+ }
+ }
+ // If any mixing factors were included, clear the rest
+ // If none were included, preserve the last mix
+ if (mix_bits) {
+ MIXER_STEPPER_LOOP(i)
+ if (!TEST(mix_bits, i)) mixer.set_collector(i, 0.0f);
+ mixer.normalize();
+ }
+ }
+
+#endif // DIRECT_MIXING_IN_G1
+
+#endif // MIXING_EXTRUDER
diff --git a/Marlin/src/gcode/feature/mixing/M166.cpp b/Marlin/src/gcode/feature/mixing/M166.cpp
new file mode 100644
index 0000000..9e071a4
--- /dev/null
+++ b/Marlin/src/gcode/feature/mixing/M166.cpp
@@ -0,0 +1,103 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(GRADIENT_MIX)
+
+#include "../../gcode.h"
+#include "../../../module/motion.h"
+#include "../../../module/planner.h"
+#include "../../../feature/mixing.h"
+
+inline void echo_mix() {
+ SERIAL_ECHOPAIR(" (", int(mixer.mix[0]), "%|", int(mixer.mix[1]), "%)");
+}
+
+inline void echo_zt(const int t, const float &z) {
+ mixer.update_mix_from_vtool(t);
+ SERIAL_ECHOPAIR_P(SP_Z_STR, z, SP_T_STR, t);
+ echo_mix();
+}
+
+/**
+ * M166: Set a simple gradient mix for a two-component mixer
+ * based on the Geeetech A10M implementation by Jone Liu.
+ *
+ * S[bool] - Enable / disable gradients
+ * A[float] - Starting Z for the gradient
+ * Z[float] - Ending Z for the gradient. (Must be greater than the starting Z.)
+ * I[index] - V-Tool to use as the starting mix.
+ * J[index] - V-Tool to use as the ending mix.
+ *
+ * T[index] - A V-Tool index to use as an alias for the Gradient (Requires GRADIENT_VTOOL)
+ * T with no index clears the setting. Note: This can match the I or J value.
+ *
+ * Example: M166 S1 A0 Z20 I0 J1
+ */
+void GcodeSuite::M166() {
+ if (parser.seenval('A')) mixer.gradient.start_z = parser.value_float();
+ if (parser.seenval('Z')) mixer.gradient.end_z = parser.value_float();
+ if (parser.seenval('I')) mixer.gradient.start_vtool = (uint8_t)constrain(parser.value_int(), 0, MIXING_VIRTUAL_TOOLS);
+ if (parser.seenval('J')) mixer.gradient.end_vtool = (uint8_t)constrain(parser.value_int(), 0, MIXING_VIRTUAL_TOOLS);
+
+ #if ENABLED(GRADIENT_VTOOL)
+ if (parser.seen('T')) mixer.gradient.vtool_index = parser.byteval('T', -1);
+ #endif
+
+ if (parser.seen('S')) mixer.gradient.enabled = parser.value_bool();
+
+ mixer.refresh_gradient();
+
+ SERIAL_ECHOPGM("Gradient Mix ");
+ serialprint_onoff(mixer.gradient.enabled);
+ if (mixer.gradient.enabled) {
+
+ #if ENABLED(GRADIENT_VTOOL)
+ if (mixer.gradient.vtool_index >= 0) {
+ SERIAL_ECHOPAIR(" (T", int(mixer.gradient.vtool_index));
+ SERIAL_CHAR(')');
+ }
+ #endif
+
+ SERIAL_ECHOPGM(" ; Start");
+ echo_zt(mixer.gradient.start_vtool, mixer.gradient.start_z);
+
+ SERIAL_ECHOPGM(" ; End");
+ echo_zt(mixer.gradient.end_vtool, mixer.gradient.end_z);
+
+ mixer.update_mix_from_gradient();
+
+ SERIAL_ECHOPGM(" ; Current Z");
+ #if ENABLED(DELTA)
+ get_cartesian_from_steppers();
+ SERIAL_ECHO(cartes.z);
+ #else
+ SERIAL_ECHO(planner.get_axis_position_mm(Z_AXIS));
+ #endif
+ echo_mix();
+ }
+
+ SERIAL_EOL();
+}
+
+#endif // GRADIENT_MIX
diff --git a/Marlin/src/gcode/feature/network/M552-M554.cpp b/Marlin/src/gcode/feature/network/M552-M554.cpp
new file mode 100644
index 0000000..6ea15fe
--- /dev/null
+++ b/Marlin/src/gcode/feature/network/M552-M554.cpp
@@ -0,0 +1,126 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if HAS_ETHERNET
+
+#include "../../../feature/ethernet.h"
+#include "../../../core/serial.h"
+#include "../../gcode.h"
+
+void say_ethernet() { SERIAL_ECHOPGM(" Ethernet "); }
+
+void ETH0_report() {
+ say_ethernet();
+ SERIAL_ECHO_TERNARY(ethernet.hardware_enabled, "port ", "en", "dis", "abled.\n");
+ if (ethernet.hardware_enabled) {
+ say_ethernet();
+ SERIAL_ECHO_TERNARY(ethernet.have_telnet_client, "client ", "en", "dis", "abled.\n");
+ }
+ else
+ SERIAL_ECHOLNPGM("Send 'M552 S1' to enable.");
+}
+
+void MAC_report() {
+ uint8_t mac[6];
+ if (ethernet.hardware_enabled) {
+ Ethernet.MACAddress(mac);
+ SERIAL_ECHOPGM(" MAC: ");
+ LOOP_L_N(i, 6) {
+ if (mac[i] < 16) SERIAL_CHAR('0');
+ SERIAL_PRINT(mac[i], HEX);
+ if (i < 5) SERIAL_CHAR(':');
+ }
+ }
+ SERIAL_EOL();
+}
+
+// Display current values when the link is active,
+// otherwise show the stored values
+void ip_report(const uint16_t cmd, PGM_P const post, const IPAddress &ipo) {
+ SERIAL_CHAR('M'); SERIAL_ECHO(cmd); SERIAL_CHAR(' ');
+ LOOP_L_N(i, 4) {
+ SERIAL_ECHO(ipo[i]);
+ if (i < 3) SERIAL_CHAR('.');
+ }
+ SERIAL_ECHOPGM(" ; ");
+ SERIAL_ECHOPGM_P(post);
+ SERIAL_EOL();
+}
+void M552_report() {
+ ip_report(552, PSTR("ip address"), Ethernet.linkStatus() == LinkON ? Ethernet.localIP() : ethernet.ip);
+}
+void M553_report() {
+ ip_report(553, PSTR("subnet mask"), Ethernet.linkStatus() == LinkON ? Ethernet.subnetMask() : ethernet.subnet);
+}
+void M554_report() {
+ ip_report(554, PSTR("gateway"), Ethernet.linkStatus() == LinkON ? Ethernet.gatewayIP() : ethernet.gateway);
+}
+
+/**
+ * M552: Set IP address, enable/disable network interface
+ *
+ * S0 : disable networking
+ * S1 : enable networking
+ * S-1 : reset network interface
+ *
+ * Pnnn : Set IP address, 0.0.0.0 means acquire an IP address using DHCP
+ */
+void GcodeSuite::M552() {
+ const bool seenP = parser.seenval('P');
+ if (seenP) ethernet.ip.fromString(parser.value_string());
+
+ const bool seenS = parser.seenval('S');
+ if (seenS) {
+ switch (parser.value_int()) {
+ case -1:
+ if (ethernet.telnetClient) ethernet.telnetClient.stop();
+ ethernet.init();
+ break;
+ case 0: ethernet.hardware_enabled = false; break;
+ case 1: ethernet.hardware_enabled = true; break;
+ default: break;
+ }
+ }
+ const bool nopar = !seenS && !seenP;
+ if (nopar || seenS) ETH0_report();
+ if (nopar || seenP) M552_report();
+}
+
+/**
+ * M553 Pnnn - Set netmask
+ */
+void GcodeSuite::M553() {
+ if (parser.seenval('P')) ethernet.subnet.fromString(parser.value_string());
+ M553_report();
+}
+
+/**
+ * M554 Pnnn - Set Gateway
+ */
+void GcodeSuite::M554() {
+ if (parser.seenval('P')) ethernet.gateway.fromString(parser.value_string());
+ M554_report();
+}
+
+#endif // HAS_ETHERNET
diff --git a/Marlin/src/gcode/feature/password/M510-M512.cpp b/Marlin/src/gcode/feature/password/M510-M512.cpp
new file mode 100644
index 0000000..eeb9b1d
--- /dev/null
+++ b/Marlin/src/gcode/feature/password/M510-M512.cpp
@@ -0,0 +1,83 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(PASSWORD_FEATURE)
+
+#include "../../../feature/password/password.h"
+#include "../../../core/serial.h"
+#include "../../gcode.h"
+
+//
+// M510: Lock Printer
+//
+void GcodeSuite::M510() {
+ password.lock_machine();
+}
+
+//
+// M511: Unlock Printer
+//
+#if ENABLED(PASSWORD_UNLOCK_GCODE)
+
+ void GcodeSuite::M511() {
+ if (password.is_locked) {
+ password.value_entry = parser.ulongval('P');
+ password.authentication_check();
+ }
+ }
+
+#endif // PASSWORD_UNLOCK_GCODE
+
+//
+// M512: Set/Change/Remove Password
+//
+#if ENABLED(PASSWORD_CHANGE_GCODE)
+
+ void GcodeSuite::M512() {
+ if (password.is_set && parser.ulongval('P') != password.value) {
+ SERIAL_ECHOLNPGM(STR_WRONG_PASSWORD);
+ return;
+ }
+
+ if (parser.seenval('S')) {
+ password.value_entry = parser.ulongval('S');
+
+ if (password.value_entry < CAT(1e, PASSWORD_LENGTH)) {
+ password.is_set = true;
+ password.value = password.value_entry;
+ SERIAL_ECHOLNPAIR(STR_PASSWORD_SET, password.value); // TODO: Update password.string
+ }
+ else
+ SERIAL_ECHOLNPGM(STR_PASSWORD_TOO_LONG);
+ }
+ else {
+ password.is_set = false;
+ SERIAL_ECHOLNPGM(STR_PASSWORD_REMOVED);
+ }
+ SERIAL_ECHOLNPGM(STR_REMINDER_SAVE_SETTINGS);
+ }
+
+#endif // PASSWORD_CHANGE_GCODE
+
+#endif // PASSWORD_FEATURE
diff --git a/Marlin/src/gcode/feature/pause/G27.cpp b/Marlin/src/gcode/feature/pause/G27.cpp
new file mode 100644
index 0000000..3ce618d
--- /dev/null
+++ b/Marlin/src/gcode/feature/pause/G27.cpp
@@ -0,0 +1,41 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(NOZZLE_PARK_FEATURE)
+
+#include "../../gcode.h"
+#include "../../../libs/nozzle.h"
+#include "../../../module/motion.h"
+
+/**
+ * G27: Park the nozzle
+ */
+void GcodeSuite::G27() {
+ // Don't allow nozzle parking without homing first
+ if (homing_needed_error()) return;
+ nozzle.park(parser.ushortval('P'));
+}
+
+#endif // NOZZLE_PARK_FEATURE
diff --git a/Marlin/src/gcode/feature/pause/G60.cpp b/Marlin/src/gcode/feature/pause/G60.cpp
new file mode 100644
index 0000000..6f695b9
--- /dev/null
+++ b/Marlin/src/gcode/feature/pause/G60.cpp
@@ -0,0 +1,58 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if SAVED_POSITIONS
+
+#include "../../gcode.h"
+#include "../../../module/motion.h"
+
+#define DEBUG_OUT ENABLED(SAVED_POSITIONS_DEBUG)
+#include "../../../core/debug_out.h"
+
+/**
+ * G60: Save current position
+ *
+ * S<slot> - Memory slot # (0-based) to save into (default 0)
+ */
+void GcodeSuite::G60() {
+ const uint8_t slot = parser.byteval('S');
+
+ if (slot >= SAVED_POSITIONS) {
+ SERIAL_ERROR_MSG(STR_INVALID_POS_SLOT STRINGIFY(SAVED_POSITIONS));
+ return;
+ }
+
+ stored_position[slot] = current_position;
+ SBI(saved_slots[slot >> 3], slot & 0x07);
+
+ #if ENABLED(SAVED_POSITIONS_DEBUG)
+ const xyze_pos_t &pos = stored_position[slot];
+ DEBUG_ECHOPAIR_F(STR_SAVED_POS " S", slot);
+ DEBUG_ECHOPAIR_F(" : X", pos.x);
+ DEBUG_ECHOPAIR_F_P(SP_Y_STR, pos.y);
+ DEBUG_ECHOLNPAIR_F_P(SP_Z_STR, pos.z);
+ #endif
+}
+
+#endif // SAVED_POSITIONS
diff --git a/Marlin/src/gcode/feature/pause/G61.cpp b/Marlin/src/gcode/feature/pause/G61.cpp
new file mode 100644
index 0000000..5d89af0
--- /dev/null
+++ b/Marlin/src/gcode/feature/pause/G61.cpp
@@ -0,0 +1,73 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if SAVED_POSITIONS
+
+#include "../../../module/planner.h"
+#include "../../gcode.h"
+#include "../../../module/motion.h"
+
+/**
+ * G61: Return to saved position
+ *
+ * F<rate> - Feedrate (optional) for the move back.
+ * S<slot> - Slot # (0-based) to restore from (default 0).
+ * X Y Z - Axes to restore. At least one is required.
+ */
+void GcodeSuite::G61(void) {
+
+ const uint8_t slot = parser.byteval('S');
+
+ #if SAVED_POSITIONS < 256
+ if (slot >= SAVED_POSITIONS) {
+ SERIAL_ERROR_MSG(STR_INVALID_POS_SLOT STRINGIFY(SAVED_POSITIONS));
+ return;
+ }
+ #endif
+
+ // No saved position? No axes being restored?
+ if (!TEST(saved_slots[slot >> 3], slot & 0x07) || !parser.seen("XYZ")) return;
+
+ SERIAL_ECHOPAIR(STR_RESTORING_POS " S", int(slot));
+ LOOP_XYZ(i) {
+ destination[i] = parser.seen(XYZ_CHAR(i))
+ ? stored_position[slot][i] + parser.value_axis_units((AxisEnum)i)
+ : current_position[i];
+ SERIAL_CHAR(' ', XYZ_CHAR(i));
+ SERIAL_ECHO_F(destination[i]);
+ }
+ SERIAL_EOL();
+
+ // Apply any given feedrate over 0.0
+ feedRate_t saved_feedrate = feedrate_mm_s;
+ const float fr = parser.linearval('F');
+ if (fr > 0.0) feedrate_mm_s = MMM_TO_MMS(fr);
+
+ // Move to the saved position
+ prepare_line_to_destination();
+
+ feedrate_mm_s = saved_feedrate;
+}
+
+#endif // SAVED_POSITIONS
diff --git a/Marlin/src/gcode/feature/pause/M125.cpp b/Marlin/src/gcode/feature/pause/M125.cpp
new file mode 100644
index 0000000..9391b86
--- /dev/null
+++ b/Marlin/src/gcode/feature/pause/M125.cpp
@@ -0,0 +1,90 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(PARK_HEAD_ON_PAUSE)
+
+#include "../../gcode.h"
+#include "../../parser.h"
+#include "../../../feature/pause.h"
+#include "../../../lcd/marlinui.h"
+#include "../../../module/motion.h"
+#include "../../../module/printcounter.h"
+#include "../../../sd/cardreader.h"
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ #include "../../../feature/powerloss.h"
+#endif
+
+/**
+ * M125: Store current position and move to parking 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.
+ *
+ * When not actively SD printing, M125 simply moves to the park
+ * position and waits, resuming with a button click or M108.
+ * Without PARK_HEAD_ON_PAUSE the M125 command does nothing.
+ *
+ * L<linear> = Override retract Length
+ * X<pos> = Override park position X
+ * Y<pos> = Override park position Y
+ * Z<linear> = Override Z raise
+ *
+ * With an LCD menu:
+ * P<bool> = Always show a prompt and await a response
+ */
+void GcodeSuite::M125() {
+ // Initial retract before move to filament change position
+ const float retract = -ABS(parser.seen('L') ? parser.value_axis_units(E_AXIS) : (PAUSE_PARK_RETRACT_LENGTH));
+
+ xyz_pos_t park_point = NOZZLE_PARK_POINT;
+
+ // Move XY axes to filament change position or given position
+ if (parser.seenval('X')) park_point.x = RAW_X_POSITION(parser.linearval('X'));
+ if (parser.seenval('Y')) park_point.y = RAW_X_POSITION(parser.linearval('Y'));
+
+ // Lift Z axis
+ if (parser.seenval('Z')) park_point.z = parser.linearval('Z');
+
+ #if HAS_HOTEND_OFFSET && NONE(DUAL_X_CARRIAGE, DELTA)
+ park_point += hotend_offset[active_extruder];
+ #endif
+
+ const bool sd_printing = TERN0(SDSUPPORT, IS_SD_PRINTING());
+
+ ui.pause_show_message(PAUSE_MESSAGE_PARKING, PAUSE_MODE_PAUSE_PRINT);
+
+ // If possible, show an LCD prompt with the 'P' flag
+ const bool show_lcd = TERN0(HAS_LCD_MENU, parser.boolval('P'));
+
+ if (pause_print(retract, park_point, 0, show_lcd)) {
+ TERN_(POWER_LOSS_RECOVERY, if (recovery.enabled) recovery.save(true));
+ if (ENABLED(EXTENSIBLE_UI) || !sd_printing || show_lcd) {
+ wait_for_confirmation(false, 0);
+ resume_print(0, 0, -retract, 0);
+ }
+ }
+}
+
+#endif // PARK_HEAD_ON_PAUSE
diff --git a/Marlin/src/gcode/feature/pause/M600.cpp b/Marlin/src/gcode/feature/pause/M600.cpp
new file mode 100644
index 0000000..1c282f2
--- /dev/null
+++ b/Marlin/src/gcode/feature/pause/M600.cpp
@@ -0,0 +1,172 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+
+#include "../../gcode.h"
+#include "../../../feature/pause.h"
+#include "../../../module/motion.h"
+#include "../../../module/printcounter.h"
+#include "../../../lcd/marlinui.h"
+
+#if HAS_MULTI_EXTRUDER
+ #include "../../../module/tool_change.h"
+#endif
+
+#if ENABLED(MMU2_MENUS)
+ #include "../../../lcd/menu/menu_mmu2.h"
+#endif
+
+#if ENABLED(MIXING_EXTRUDER)
+ #include "../../../feature/mixing.h"
+#endif
+
+#if HAS_FILAMENT_SENSOR
+ #include "../../../feature/runout.h"
+#endif
+
+/**
+ * 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
+ * R[temp] - Resume temperature (in current units)
+ *
+ * Default values are used for omitted arguments.
+ */
+void GcodeSuite::M600() {
+
+ #if ENABLED(MIXING_EXTRUDER)
+ const int8_t target_e_stepper = get_target_e_stepper_from_command();
+ if (target_e_stepper < 0) return;
+
+ const uint8_t old_mixing_tool = mixer.get_current_vtool();
+ mixer.T(MIXER_DIRECT_SET_TOOL);
+
+ MIXER_STEPPER_LOOP(i) mixer.set_collector(i, i == uint8_t(target_e_stepper) ? 1.0 : 0.0);
+ mixer.normalize();
+
+ const int8_t target_extruder = active_extruder;
+ #else
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ #endif
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ int8_t DXC_ext = target_extruder;
+ if (!parser.seen('T')) { // If no tool index is specified, M600 was (probably) sent in response to filament runout.
+ // In this case, for duplicating modes set DXC_ext to the extruder that ran out.
+ #if HAS_FILAMENT_SENSOR && NUM_RUNOUT_SENSORS > 1
+ if (idex_is_duplicating())
+ DXC_ext = (READ(FIL_RUNOUT2_PIN) == FIL_RUNOUT2_STATE) ? 1 : 0;
+ #else
+ DXC_ext = active_extruder;
+ #endif
+ }
+ #endif
+
+ // Show initial "wait for start" message
+ #if DISABLED(MMU2_MENUS)
+ ui.pause_show_message(PAUSE_MESSAGE_CHANGING, PAUSE_MODE_PAUSE_PRINT, target_extruder);
+ #endif
+
+ #if ENABLED(HOME_BEFORE_FILAMENT_CHANGE)
+ // If needed, home before parking for filament change
+ if (!all_axes_trusted()) home_all_axes(true);
+ #endif
+
+ #if HAS_MULTI_EXTRUDER
+ // Change toolhead if specified
+ const uint8_t active_extruder_before_filament_change = active_extruder;
+ if (active_extruder != target_extruder && TERN1(DUAL_X_CARRIAGE, !idex_is_duplicating()))
+ tool_change(target_extruder, false);
+ #endif
+
+ // Initial retract before move to filament change position
+ const float retract = -ABS(parser.seen('E') ? parser.value_axis_units(E_AXIS) : (PAUSE_PARK_RETRACT_LENGTH));
+
+ xyz_pos_t park_point NOZZLE_PARK_POINT;
+
+ // 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 HAS_HOTEND_OFFSET && NONE(DUAL_X_CARRIAGE, DELTA)
+ park_point += hotend_offset[active_extruder];
+ #endif
+
+ #if ENABLED(MMU2_MENUS)
+ // For MMU2 reset retract and load/unload values so they don't mess with MMU filament handling
+ constexpr float unload_length = 0.5f,
+ slow_load_length = 0.0f,
+ fast_load_length = 0.0f;
+ #else
+ // Unload filament
+ const float unload_length = -ABS(parser.seen('U') ? parser.value_axis_units(E_AXIS)
+ : fc_settings[active_extruder].unload_length);
+
+ // Slow load filament
+ constexpr float slow_load_length = FILAMENT_CHANGE_SLOW_LOAD_LENGTH;
+
+ // Fast load filament
+ const float fast_load_length = ABS(parser.seen('L') ? parser.value_axis_units(E_AXIS)
+ : fc_settings[active_extruder].load_length);
+ #endif
+
+ const int beep_count = parser.intval('B', -1
+ #ifdef FILAMENT_CHANGE_ALERT_BEEPS
+ + 1 + FILAMENT_CHANGE_ALERT_BEEPS
+ #endif
+ );
+
+ if (pause_print(retract, park_point, unload_length, true DXC_PASS)) {
+ #if ENABLED(MMU2_MENUS)
+ mmu2_M600();
+ resume_print(slow_load_length, fast_load_length, 0, beep_count DXC_PASS);
+ #else
+ wait_for_confirmation(true, beep_count DXC_PASS);
+ resume_print(slow_load_length, fast_load_length, ADVANCED_PAUSE_PURGE_LENGTH,
+ beep_count, (parser.seenval('R') ? parser.value_celsius() : 0) DXC_PASS);
+ #endif
+ }
+
+ #if HAS_MULTI_EXTRUDER
+ // Restore toolhead if it was changed
+ if (active_extruder_before_filament_change != active_extruder)
+ tool_change(active_extruder_before_filament_change, false);
+ #endif
+
+ TERN_(MIXING_EXTRUDER, mixer.T(old_mixing_tool)); // Restore original mixing tool
+}
+
+#endif // ADVANCED_PAUSE_FEATURE
diff --git a/Marlin/src/gcode/feature/pause/M603.cpp b/Marlin/src/gcode/feature/pause/M603.cpp
new file mode 100644
index 0000000..9c3b774
--- /dev/null
+++ b/Marlin/src/gcode/feature/pause/M603.cpp
@@ -0,0 +1,65 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(ADVANCED_PAUSE_FEATURE)
+
+#include "../../gcode.h"
+#include "../../../feature/pause.h"
+#include "../../../module/motion.h"
+#include "../../../module/printcounter.h"
+
+#if HAS_MULTI_EXTRUDER
+ #include "../../../module/tool_change.h"
+#endif
+
+/**
+ * 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
+ */
+void GcodeSuite::M603() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ // Unload length
+ if (parser.seen('U')) {
+ fc_settings[target_extruder].unload_length = ABS(parser.value_axis_units(E_AXIS));
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ NOMORE(fc_settings[target_extruder].unload_length, EXTRUDE_MAXLENGTH);
+ #endif
+ }
+
+ // Load length
+ if (parser.seen('L')) {
+ fc_settings[target_extruder].load_length = ABS(parser.value_axis_units(E_AXIS));
+ #if ENABLED(PREVENT_LENGTHY_EXTRUDE)
+ NOMORE(fc_settings[target_extruder].load_length, EXTRUDE_MAXLENGTH);
+ #endif
+ }
+}
+
+#endif // ADVANCED_PAUSE_FEATURE
diff --git a/Marlin/src/gcode/feature/pause/M701_M702.cpp b/Marlin/src/gcode/feature/pause/M701_M702.cpp
new file mode 100644
index 0000000..9a2b774
--- /dev/null
+++ b/Marlin/src/gcode/feature/pause/M701_M702.cpp
@@ -0,0 +1,235 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+
+#include "../../gcode.h"
+#include "../../../MarlinCore.h"
+#include "../../../module/motion.h"
+#include "../../../module/temperature.h"
+#include "../../../feature/pause.h"
+#include "../../../lcd/marlinui.h"
+
+#if HAS_MULTI_EXTRUDER
+ #include "../../../module/tool_change.h"
+#endif
+
+#if HAS_PRUSA_MMU2
+ #include "../../../feature/mmu/mmu2.h"
+#endif
+
+#if ENABLED(MIXING_EXTRUDER)
+ #include "../../../feature/mixing.h"
+#endif
+
+/**
+ * M701: Load filament
+ *
+ * T<extruder> - Extruder number. Required for mixing extruder.
+ * For non-mixing, 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.
+ */
+void GcodeSuite::M701() {
+ xyz_pos_t park_point = NOZZLE_PARK_POINT;
+
+ // Don't raise Z if the machine isn't homed
+ if (TERN0(NO_MOTION_BEFORE_HOMING, axes_should_home())) park_point.z = 0;
+
+ #if ENABLED(MIXING_EXTRUDER)
+ const int8_t target_e_stepper = get_target_e_stepper_from_command();
+ if (target_e_stepper < 0) return;
+
+ const uint8_t old_mixing_tool = mixer.get_current_vtool();
+ mixer.T(MIXER_DIRECT_SET_TOOL);
+
+ MIXER_STEPPER_LOOP(i) mixer.set_collector(i, (i == (uint8_t)target_e_stepper) ? 1.0 : 0.0);
+ mixer.normalize();
+
+ const int8_t target_extruder = active_extruder;
+ #else
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ #endif
+
+ // Z axis lift
+ if (parser.seenval('Z')) park_point.z = parser.linearval('Z');
+
+ // Show initial "wait for load" message
+ ui.pause_show_message(PAUSE_MESSAGE_LOAD, PAUSE_MODE_LOAD_FILAMENT, target_extruder);
+
+ #if HAS_MULTI_EXTRUDER && (HAS_PRUSA_MMU1 || !HAS_MMU)
+ // Change toolhead if specified
+ uint8_t active_extruder_before_filament_change = active_extruder;
+ if (active_extruder != target_extruder)
+ tool_change(target_extruder, false);
+ #endif
+
+ // Lift Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(_MIN(current_position.z + park_point.z, Z_MAX_POS), feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
+
+ // Load filament
+ #if HAS_PRUSA_MMU2
+ mmu2.load_filament_to_nozzle(target_extruder);
+ #else
+ constexpr float purge_length = ADVANCED_PAUSE_PURGE_LENGTH,
+ slow_load_length = FILAMENT_CHANGE_SLOW_LOAD_LENGTH;
+ const float fast_load_length = ABS(parser.seen('L') ? parser.value_axis_units(E_AXIS)
+ : fc_settings[active_extruder].load_length);
+ load_filament(
+ slow_load_length, fast_load_length, purge_length,
+ FILAMENT_CHANGE_ALERT_BEEPS,
+ true, // show_lcd
+ thermalManager.still_heating(target_extruder), // pause_for_user
+ PAUSE_MODE_LOAD_FILAMENT // pause_mode
+ #if ENABLED(DUAL_X_CARRIAGE)
+ , target_extruder // Dual X target
+ #endif
+ );
+ #endif
+
+ // Restore Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(_MAX(current_position.z - park_point.z, 0), feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
+
+ #if HAS_MULTI_EXTRUDER && (HAS_PRUSA_MMU1 || !HAS_MMU)
+ // Restore toolhead if it was changed
+ if (active_extruder_before_filament_change != active_extruder)
+ tool_change(active_extruder_before_filament_change, false);
+ #endif
+
+ TERN_(MIXING_EXTRUDER, mixer.T(old_mixing_tool)); // Restore original mixing tool
+
+ // Show status screen
+ ui.pause_show_message(PAUSE_MESSAGE_STATUS);
+}
+
+/**
+ * M702: Unload filament
+ *
+ * T<extruder> - Extruder number. Required for mixing extruder.
+ * For non-mixing, 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.
+ */
+void GcodeSuite::M702() {
+ xyz_pos_t park_point = NOZZLE_PARK_POINT;
+
+ // Don't raise Z if the machine isn't homed
+ if (TERN0(NO_MOTION_BEFORE_HOMING, axes_should_home())) park_point.z = 0;
+
+ #if ENABLED(MIXING_EXTRUDER)
+ const uint8_t old_mixing_tool = mixer.get_current_vtool();
+
+ #if ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS)
+ float mix_multiplier = 1.0;
+ const bool seenT = parser.seenval('T');
+ if (!seenT) {
+ mixer.T(MIXER_AUTORETRACT_TOOL);
+ mix_multiplier = MIXING_STEPPERS;
+ }
+ #else
+ constexpr bool seenT = true;
+ #endif
+
+ if (seenT) {
+ const int8_t target_e_stepper = get_target_e_stepper_from_command();
+ if (target_e_stepper < 0) return;
+ mixer.T(MIXER_DIRECT_SET_TOOL);
+ MIXER_STEPPER_LOOP(i) mixer.set_collector(i, (i == (uint8_t)target_e_stepper) ? 1.0 : 0.0);
+ mixer.normalize();
+ }
+
+ const int8_t target_extruder = active_extruder;
+ #else
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ #endif
+
+ // Z axis lift
+ if (parser.seenval('Z')) park_point.z = parser.linearval('Z');
+
+ // Show initial "wait for unload" message
+ ui.pause_show_message(PAUSE_MESSAGE_UNLOAD, PAUSE_MODE_UNLOAD_FILAMENT, target_extruder);
+
+ #if HAS_MULTI_EXTRUDER && (HAS_PRUSA_MMU1 || !HAS_MMU)
+ // Change toolhead if specified
+ uint8_t active_extruder_before_filament_change = active_extruder;
+ if (active_extruder != target_extruder)
+ tool_change(target_extruder, false);
+ #endif
+
+ // Lift Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(_MIN(current_position.z + park_point.z, Z_MAX_POS), feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
+
+ // Unload filament
+ #if HAS_PRUSA_MMU2
+ mmu2.unload();
+ #else
+ #if BOTH(HAS_MULTI_EXTRUDER, FILAMENT_UNLOAD_ALL_EXTRUDERS)
+ if (!parser.seenval('T')) {
+ HOTEND_LOOP() {
+ if (e != active_extruder) tool_change(e, false);
+ unload_filament(-fc_settings[e].unload_length, true, PAUSE_MODE_UNLOAD_FILAMENT);
+ }
+ }
+ else
+ #endif
+ {
+ // Unload length
+ const float unload_length = -ABS(parser.seen('U') ? parser.value_axis_units(E_AXIS)
+ : fc_settings[target_extruder].unload_length);
+
+ unload_filament(unload_length, true, PAUSE_MODE_UNLOAD_FILAMENT
+ #if ALL(FILAMENT_UNLOAD_ALL_EXTRUDERS, MIXING_EXTRUDER)
+ , mix_multiplier
+ #endif
+ );
+ }
+ #endif
+
+ // Restore Z axis
+ if (park_point.z > 0)
+ do_blocking_move_to_z(_MAX(current_position.z - park_point.z, 0), feedRate_t(NOZZLE_PARK_Z_FEEDRATE));
+
+ #if HAS_MULTI_EXTRUDER && (HAS_PRUSA_MMU1 || !HAS_MMU)
+ // Restore toolhead if it was changed
+ if (active_extruder_before_filament_change != active_extruder)
+ tool_change(active_extruder_before_filament_change, false);
+ #endif
+
+ TERN_(MIXING_EXTRUDER, mixer.T(old_mixing_tool)); // Restore original mixing tool
+
+ // Show status screen
+ ui.pause_show_message(PAUSE_MESSAGE_STATUS);
+}
+
+#endif // ADVANCED_PAUSE_FEATURE
diff --git a/Marlin/src/gcode/feature/power_monitor/M430.cpp b/Marlin/src/gcode/feature/power_monitor/M430.cpp
new file mode 100644
index 0000000..9559404
--- /dev/null
+++ b/Marlin/src/gcode/feature/power_monitor/M430.cpp
@@ -0,0 +1,70 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_POWER_MONITOR
+
+#include "../../../feature/power_monitor.h"
+#include "../../../MarlinCore.h"
+#include "../../gcode.h"
+
+/**
+ * M430: Enable/disable current LCD display
+ * With no parameters report the system current draw (in Amps)
+ *
+ * I[bool] - Set Display of current on the LCD
+ * V[bool] - Set Display of voltage on the LCD
+ * W[bool] - Set Display of power on the LCD
+ */
+void GcodeSuite::M430() {
+ bool do_report = true;
+ #if HAS_WIRED_LCD
+ #if ENABLED(POWER_MONITOR_CURRENT)
+ if (parser.seen('I')) { power_monitor.set_current_display(parser.value_bool()); do_report = false; }
+ #endif
+ #if HAS_POWER_MONITOR_VREF
+ if (parser.seen('V')) { power_monitor.set_voltage_display(parser.value_bool()); do_report = false; }
+ #endif
+ #if HAS_POWER_MONITOR_WATTS
+ if (parser.seen('W')) { power_monitor.set_power_display(parser.value_bool()); do_report = false; }
+ #endif
+ #endif
+ if (do_report) {
+ SERIAL_ECHOLNPAIR(
+ #if ENABLED(POWER_MONITOR_CURRENT)
+ "Current: ", power_monitor.getAmps(), "A"
+ #if HAS_POWER_MONITOR_VREF
+ " "
+ #endif
+ #endif
+ #if HAS_POWER_MONITOR_VREF
+ "Voltage: ", power_monitor.getVolts(), "V"
+ #endif
+ #if HAS_POWER_MONITOR_WATTS
+ " Power: ", power_monitor.getPower(), "W"
+ #endif
+ );
+ }
+}
+
+#endif // HAS_POWER_MONITOR
diff --git a/Marlin/src/gcode/feature/powerloss/M1000.cpp b/Marlin/src/gcode/feature/powerloss/M1000.cpp
new file mode 100644
index 0000000..14c9253
--- /dev/null
+++ b/Marlin/src/gcode/feature/powerloss/M1000.cpp
@@ -0,0 +1,89 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+
+#include "../../gcode.h"
+#include "../../../feature/powerloss.h"
+#include "../../../module/motion.h"
+#include "../../../lcd/marlinui.h"
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../../lcd/extui/ui_api.h"
+#endif
+
+#define DEBUG_OUT ENABLED(DEBUG_POWER_LOSS_RECOVERY)
+#include "../../../core/debug_out.h"
+
+void menu_job_recovery();
+
+inline void plr_error(PGM_P const prefix) {
+ #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
+ DEBUG_ECHO_START();
+ serialprintPGM(prefix);
+ DEBUG_ECHOLNPGM(" Job Recovery Data");
+ #else
+ UNUSED(prefix);
+ #endif
+}
+
+#if HAS_LCD_MENU
+ void lcd_power_loss_recovery_cancel();
+#endif
+
+/**
+ * M1000: Resume from power-loss (undocumented)
+ * - With 'S' go to the Resume/Cancel menu
+ * - With no parameters, run recovery commands
+ */
+void GcodeSuite::M1000() {
+
+ if (recovery.valid()) {
+ if (parser.seen('S')) {
+ #if HAS_LCD_MENU
+ ui.goto_screen(menu_job_recovery);
+ #elif ENABLED(DWIN_CREALITY_LCD)
+ recovery.dwin_flag = true;
+ #elif ENABLED(EXTENSIBLE_UI)
+ ExtUI::onPowerLossResume();
+ #else
+ SERIAL_ECHO_MSG("Resume requires LCD.");
+ #endif
+ }
+ else if (parser.seen('C')) {
+ #if HAS_LCD_MENU
+ lcd_power_loss_recovery_cancel();
+ #else
+ recovery.cancel();
+ #endif
+ TERN_(EXTENSIBLE_UI, ExtUI::onPrintTimerStopped());
+ }
+ else
+ recovery.resume();
+ }
+ else
+ plr_error(recovery.info.valid_head ? PSTR("No") : PSTR("Invalid"));
+
+}
+
+#endif // POWER_LOSS_RECOVERY
diff --git a/Marlin/src/gcode/feature/powerloss/M413.cpp b/Marlin/src/gcode/feature/powerloss/M413.cpp
new file mode 100644
index 0000000..64573e5
--- /dev/null
+++ b/Marlin/src/gcode/feature/powerloss/M413.cpp
@@ -0,0 +1,62 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+
+#include "../../gcode.h"
+#include "../../../feature/powerloss.h"
+#include "../../../module/motion.h"
+#include "../../../lcd/marlinui.h"
+
+/**
+ * M413: Enable / Disable power-loss recovery
+ *
+ * Parameters
+ * S[bool] - Flag to enable / disable.
+ * If omitted, report current state.
+ */
+void GcodeSuite::M413() {
+
+ if (parser.seen('S'))
+ recovery.enable(parser.value_bool());
+ else {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("Power-loss recovery ");
+ serialprintln_onoff(recovery.enabled);
+ }
+
+ #if ENABLED(DEBUG_POWER_LOSS_RECOVERY)
+ if (parser.seen("RL")) recovery.load();
+ if (parser.seen('W')) recovery.save(true);
+ if (parser.seen('P')) recovery.purge();
+ if (parser.seen('D')) recovery.debug(PSTR("M413"));
+ #if PIN_EXISTS(POWER_LOSS)
+ if (parser.seen('O')) recovery._outage();
+ #endif
+ if (parser.seen('E')) serialprintPGM(recovery.exists() ? PSTR("PLR Exists\n") : PSTR("No PLR\n"));
+ if (parser.seen('V')) serialprintPGM(recovery.valid() ? PSTR("Valid\n") : PSTR("Invalid\n"));
+ #endif
+}
+
+#endif // POWER_LOSS_RECOVERY
diff --git a/Marlin/src/gcode/feature/prusa_MMU2/M403.cpp b/Marlin/src/gcode/feature/prusa_MMU2/M403.cpp
new file mode 100644
index 0000000..31d0763
--- /dev/null
+++ b/Marlin/src/gcode/feature/prusa_MMU2/M403.cpp
@@ -0,0 +1,49 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfigPre.h"
+
+#if HAS_PRUSA_MMU2
+
+#include "../../gcode.h"
+#include "../../../feature/mmu/mmu2.h"
+
+/**
+ * M403: Set filament type for MMU2
+ *
+ * Valid filament type values:
+ *
+ * 0 Default
+ * 1 Flexible
+ * 2 PVA
+ */
+void GcodeSuite::M403() {
+ int8_t index = parser.intval('E', -1),
+ type = parser.intval('F', -1);
+
+ if (WITHIN(index, 0, 4) && WITHIN(type, 0, 2))
+ mmu2.set_filament_type(index, type);
+ else
+ SERIAL_ECHO_MSG("M403 - bad arguments.");
+}
+
+#endif // HAS_PRUSA_MMU2
diff --git a/Marlin/src/gcode/feature/runout/M412.cpp b/Marlin/src/gcode/feature/runout/M412.cpp
new file mode 100644
index 0000000..130f9c8
--- /dev/null
+++ b/Marlin/src/gcode/feature/runout/M412.cpp
@@ -0,0 +1,64 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_FILAMENT_SENSOR
+
+#include "../../gcode.h"
+#include "../../../feature/runout.h"
+
+/**
+ * M412: Enable / Disable filament runout detection
+ *
+ * Parameters
+ * R : Reset the runout sensor
+ * S<bool> : Reset and enable/disable the runout sensor
+ * H<bool> : Enable/disable host handling of filament runout
+ * D<linear> : Extra distance to continue after runout is triggered
+ */
+void GcodeSuite::M412() {
+ if (parser.seen("RS"
+ TERN_(HAS_FILAMENT_RUNOUT_DISTANCE, "D")
+ TERN_(HOST_ACTION_COMMANDS, "H")
+ )) {
+ #if ENABLED(HOST_ACTION_COMMANDS)
+ if (parser.seen('H')) runout.host_handling = parser.value_bool();
+ #endif
+ const bool seenR = parser.seen('R'), seenS = parser.seen('S');
+ if (seenR || seenS) runout.reset();
+ if (seenS) runout.enabled = parser.value_bool();
+ #if HAS_FILAMENT_RUNOUT_DISTANCE
+ if (parser.seen('D')) runout.set_runout_distance(parser.value_linear_units());
+ #endif
+ }
+ else {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("Filament runout ");
+ serialprintln_onoff(runout.enabled);
+ #if HAS_FILAMENT_RUNOUT_DISTANCE
+ SERIAL_ECHOLNPAIR("Filament runout distance (mm): ", runout.runout_distance());
+ #endif
+ }
+}
+
+#endif // HAS_FILAMENT_SENSOR
diff --git a/Marlin/src/gcode/feature/trinamic/M122.cpp b/Marlin/src/gcode/feature/trinamic/M122.cpp
new file mode 100644
index 0000000..46e4365
--- /dev/null
+++ b/Marlin/src/gcode/feature/trinamic/M122.cpp
@@ -0,0 +1,60 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_TRINAMIC_CONFIG
+
+#include "../../gcode.h"
+#include "../../../feature/tmc_util.h"
+#include "../../../module/stepper/indirection.h"
+
+/**
+ * M122: Debug TMC drivers
+ */
+void GcodeSuite::M122() {
+ xyze_bool_t print_axis = { false, false, false, false };
+ bool print_all = true;
+ LOOP_XYZE(i) if (parser.seen(axis_codes[i])) { print_axis[i] = true; print_all = false; }
+
+ if (print_all) LOOP_XYZE(i) print_axis[i] = true;
+
+ if (parser.boolval('I')) restore_stepper_drivers();
+
+ #if ENABLED(TMC_DEBUG)
+ #if ENABLED(MONITOR_DRIVER_STATUS)
+ uint16_t interval = MONITOR_DRIVER_STATUS_INTERVAL_MS;
+ if (parser.seen('S') && !parser.value_bool()) interval = 0;
+ if (parser.seenval('P')) NOMORE(interval, parser.value_ushort());
+ tmc_set_report_interval(interval);
+ #endif
+
+ if (parser.seen('V'))
+ tmc_get_registers(print_axis.x, print_axis.y, print_axis.z, print_axis.e);
+ else
+ tmc_report_all(print_axis.x, print_axis.y, print_axis.z, print_axis.e);
+ #endif
+
+ test_tmc_connection(print_axis.x, print_axis.y, print_axis.z, print_axis.e);
+}
+
+#endif // HAS_TRINAMIC_CONFIG
diff --git a/Marlin/src/gcode/feature/trinamic/M569.cpp b/Marlin/src/gcode/feature/trinamic/M569.cpp
new file mode 100644
index 0000000..6b379f1
--- /dev/null
+++ b/Marlin/src/gcode/feature/trinamic/M569.cpp
@@ -0,0 +1,186 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_STEALTHCHOP
+
+#include "../../gcode.h"
+#include "../../../feature/tmc_util.h"
+#include "../../../module/stepper/indirection.h"
+
+template<typename TMC>
+void tmc_say_stealth_status(TMC &st) {
+ st.printLabel();
+ SERIAL_ECHOPGM(" driver mode:\t");
+ serialprintPGM(st.get_stealthChop() ? PSTR("stealthChop") : PSTR("spreadCycle"));
+ SERIAL_EOL();
+}
+template<typename TMC>
+void tmc_set_stealthChop(TMC &st, const bool enable) {
+ st.stored.stealthChop_enabled = enable;
+ st.refresh_stepping_mode();
+}
+
+static void set_stealth_status(const bool enable, const int8_t target_extruder) {
+ #define TMC_SET_STEALTH(Q) tmc_set_stealthChop(stepper##Q, enable)
+
+ #if AXIS_HAS_STEALTHCHOP(X) || AXIS_HAS_STEALTHCHOP(X2) \
+ || AXIS_HAS_STEALTHCHOP(Y) || AXIS_HAS_STEALTHCHOP(Y2) \
+ || AXIS_HAS_STEALTHCHOP(Z) || AXIS_HAS_STEALTHCHOP(Z2) \
+ || AXIS_HAS_STEALTHCHOP(Z3) || AXIS_HAS_STEALTHCHOP(Z4)
+ const uint8_t index = parser.byteval('I');
+ #endif
+
+ LOOP_XYZE(i) if (parser.seen(axis_codes[i])) {
+ switch (i) {
+ case X_AXIS:
+ #if AXIS_HAS_STEALTHCHOP(X)
+ if (index == 0) TMC_SET_STEALTH(X);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(X2)
+ if (index == 1) TMC_SET_STEALTH(X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if AXIS_HAS_STEALTHCHOP(Y)
+ if (index == 0) TMC_SET_STEALTH(Y);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y2)
+ if (index == 1) TMC_SET_STEALTH(Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if AXIS_HAS_STEALTHCHOP(Z)
+ if (index == 0) TMC_SET_STEALTH(Z);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z2)
+ if (index == 1) TMC_SET_STEALTH(Z2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z3)
+ if (index == 2) TMC_SET_STEALTH(Z3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z4)
+ if (index == 3) TMC_SET_STEALTH(Z4);
+ #endif
+ break;
+ case E_AXIS: {
+ if (target_extruder < 0) return;
+ switch (target_extruder) {
+ #if AXIS_HAS_STEALTHCHOP(E0)
+ case 0: TMC_SET_STEALTH(E0); break;
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E1)
+ case 1: TMC_SET_STEALTH(E1); break;
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E2)
+ case 2: TMC_SET_STEALTH(E2); break;
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E3)
+ case 3: TMC_SET_STEALTH(E3); break;
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E4)
+ case 4: TMC_SET_STEALTH(E4); break;
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E5)
+ case 5: TMC_SET_STEALTH(E5); break;
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E6)
+ case 6: TMC_SET_STEALTH(E6); break;
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E7)
+ case 7: TMC_SET_STEALTH(E7); break;
+ #endif
+ }
+ } break;
+ }
+ }
+}
+
+static void say_stealth_status() {
+ #define TMC_SAY_STEALTH_STATUS(Q) tmc_say_stealth_status(stepper##Q)
+
+ #if AXIS_HAS_STEALTHCHOP(X)
+ TMC_SAY_STEALTH_STATUS(X);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(X2)
+ TMC_SAY_STEALTH_STATUS(X2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y)
+ TMC_SAY_STEALTH_STATUS(Y);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y2)
+ TMC_SAY_STEALTH_STATUS(Y2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z)
+ TMC_SAY_STEALTH_STATUS(Z);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z2)
+ TMC_SAY_STEALTH_STATUS(Z2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z3)
+ TMC_SAY_STEALTH_STATUS(Z3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z4)
+ TMC_SAY_STEALTH_STATUS(Z4);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E0)
+ TMC_SAY_STEALTH_STATUS(E0);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E1)
+ TMC_SAY_STEALTH_STATUS(E1);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E2)
+ TMC_SAY_STEALTH_STATUS(E2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E3)
+ TMC_SAY_STEALTH_STATUS(E3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E4)
+ TMC_SAY_STEALTH_STATUS(E4);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E5)
+ TMC_SAY_STEALTH_STATUS(E5);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E6)
+ TMC_SAY_STEALTH_STATUS(E6);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(E7)
+ TMC_SAY_STEALTH_STATUS(E7);
+ #endif
+}
+
+/**
+ * M569: Enable stealthChop on an axis
+ *
+ * S[1|0] to enable or disable
+ * XYZE to target an axis
+ * No arguments reports the stealthChop status of all capable drivers.
+ */
+void GcodeSuite::M569() {
+ if (parser.seen('S'))
+ set_stealth_status(parser.value_bool(), get_target_extruder_from_command());
+ else
+ say_stealth_status();
+}
+
+#endif // HAS_STEALTHCHOP
diff --git a/Marlin/src/gcode/feature/trinamic/M906.cpp b/Marlin/src/gcode/feature/trinamic/M906.cpp
new file mode 100644
index 0000000..e834ebd
--- /dev/null
+++ b/Marlin/src/gcode/feature/trinamic/M906.cpp
@@ -0,0 +1,173 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_TRINAMIC_CONFIG
+
+#include "../../gcode.h"
+#include "../../../feature/tmc_util.h"
+#include "../../../module/stepper/indirection.h"
+
+/**
+ * M906: Set motor current in milliamps.
+ *
+ * Parameters:
+ * X[current] - Set mA current for X driver(s)
+ * Y[current] - Set mA current for Y driver(s)
+ * Z[current] - Set mA current for Z driver(s)
+ * E[current] - Set mA current for E driver(s)
+ *
+ * I[index] - Axis sub-index (Omit or 0 for X, Y, Z; 1 for X2, Y2, Z2; 2 for Z3; 3 for Z4.)
+ * T[index] - Extruder index (Zero-based. Omit for E0 only.)
+ *
+ * With no parameters report driver currents.
+ */
+void GcodeSuite::M906() {
+ #define TMC_SAY_CURRENT(Q) tmc_print_current(stepper##Q)
+ #define TMC_SET_CURRENT(Q) stepper##Q.rms_current(value)
+
+ bool report = true;
+
+ #if AXIS_IS_TMC(X) || AXIS_IS_TMC(X2) || AXIS_IS_TMC(Y) || AXIS_IS_TMC(Y2) || AXIS_IS_TMC(Z) || AXIS_IS_TMC(Z2) || AXIS_IS_TMC(Z3) || AXIS_IS_TMC(Z4)
+ const uint8_t index = parser.byteval('I');
+ #endif
+
+ LOOP_XYZE(i) if (uint16_t value = parser.intval(axis_codes[i])) {
+ report = false;
+ switch (i) {
+ case X_AXIS:
+ #if AXIS_IS_TMC(X)
+ if (index == 0) TMC_SET_CURRENT(X);
+ #endif
+ #if AXIS_IS_TMC(X2)
+ if (index == 1) TMC_SET_CURRENT(X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if AXIS_IS_TMC(Y)
+ if (index == 0) TMC_SET_CURRENT(Y);
+ #endif
+ #if AXIS_IS_TMC(Y2)
+ if (index == 1) TMC_SET_CURRENT(Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if AXIS_IS_TMC(Z)
+ if (index == 0) TMC_SET_CURRENT(Z);
+ #endif
+ #if AXIS_IS_TMC(Z2)
+ if (index == 1) TMC_SET_CURRENT(Z2);
+ #endif
+ #if AXIS_IS_TMC(Z3)
+ if (index == 2) TMC_SET_CURRENT(Z3);
+ #endif
+ #if AXIS_IS_TMC(Z4)
+ if (index == 3) TMC_SET_CURRENT(Z4);
+ #endif
+ break;
+ case E_AXIS: {
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ switch (target_extruder) {
+ #if AXIS_IS_TMC(E0)
+ case 0: TMC_SET_CURRENT(E0); break;
+ #endif
+ #if AXIS_IS_TMC(E1)
+ case 1: TMC_SET_CURRENT(E1); break;
+ #endif
+ #if AXIS_IS_TMC(E2)
+ case 2: TMC_SET_CURRENT(E2); break;
+ #endif
+ #if AXIS_IS_TMC(E3)
+ case 3: TMC_SET_CURRENT(E3); break;
+ #endif
+ #if AXIS_IS_TMC(E4)
+ case 4: TMC_SET_CURRENT(E4); break;
+ #endif
+ #if AXIS_IS_TMC(E5)
+ case 5: TMC_SET_CURRENT(E5); break;
+ #endif
+ #if AXIS_IS_TMC(E6)
+ case 6: TMC_SET_CURRENT(E6); break;
+ #endif
+ #if AXIS_IS_TMC(E7)
+ case 7: TMC_SET_CURRENT(E7); break;
+ #endif
+ }
+ } break;
+ }
+ }
+
+ if (report) {
+ #if AXIS_IS_TMC(X)
+ TMC_SAY_CURRENT(X);
+ #endif
+ #if AXIS_IS_TMC(X2)
+ TMC_SAY_CURRENT(X2);
+ #endif
+ #if AXIS_IS_TMC(Y)
+ TMC_SAY_CURRENT(Y);
+ #endif
+ #if AXIS_IS_TMC(Y2)
+ TMC_SAY_CURRENT(Y2);
+ #endif
+ #if AXIS_IS_TMC(Z)
+ TMC_SAY_CURRENT(Z);
+ #endif
+ #if AXIS_IS_TMC(Z2)
+ TMC_SAY_CURRENT(Z2);
+ #endif
+ #if AXIS_IS_TMC(Z3)
+ TMC_SAY_CURRENT(Z3);
+ #endif
+ #if AXIS_IS_TMC(Z4)
+ TMC_SAY_CURRENT(Z4);
+ #endif
+ #if AXIS_IS_TMC(E0)
+ TMC_SAY_CURRENT(E0);
+ #endif
+ #if AXIS_IS_TMC(E1)
+ TMC_SAY_CURRENT(E1);
+ #endif
+ #if AXIS_IS_TMC(E2)
+ TMC_SAY_CURRENT(E2);
+ #endif
+ #if AXIS_IS_TMC(E3)
+ TMC_SAY_CURRENT(E3);
+ #endif
+ #if AXIS_IS_TMC(E4)
+ TMC_SAY_CURRENT(E4);
+ #endif
+ #if AXIS_IS_TMC(E5)
+ TMC_SAY_CURRENT(E5);
+ #endif
+ #if AXIS_IS_TMC(E6)
+ TMC_SAY_CURRENT(E6);
+ #endif
+ #if AXIS_IS_TMC(E7)
+ TMC_SAY_CURRENT(E7);
+ #endif
+ }
+}
+
+#endif // HAS_TRINAMIC_CONFIG
diff --git a/Marlin/src/gcode/feature/trinamic/M911-M914.cpp b/Marlin/src/gcode/feature/trinamic/M911-M914.cpp
new file mode 100644
index 0000000..8c840db
--- /dev/null
+++ b/Marlin/src/gcode/feature/trinamic/M911-M914.cpp
@@ -0,0 +1,429 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../../inc/MarlinConfig.h"
+
+#if HAS_TRINAMIC_CONFIG
+
+#include "../../gcode.h"
+#include "../../../feature/tmc_util.h"
+#include "../../../module/stepper/indirection.h"
+#include "../../../module/planner.h"
+#include "../../queue.h"
+
+#if ENABLED(MONITOR_DRIVER_STATUS)
+
+ #define M91x_USE(ST) (AXIS_DRIVER_TYPE(ST, TMC2130) || AXIS_DRIVER_TYPE(ST, TMC2160) || AXIS_DRIVER_TYPE(ST, TMC2208) || AXIS_DRIVER_TYPE(ST, TMC2209) || AXIS_DRIVER_TYPE(ST, TMC2660) || AXIS_DRIVER_TYPE(ST, TMC5130) || AXIS_DRIVER_TYPE(ST, TMC5160))
+ #define M91x_USE_E(N) (E_STEPPERS > N && M91x_USE(E##N))
+
+ #define M91x_SOME_X (M91x_USE(X) || M91x_USE(X2))
+ #define M91x_SOME_Y (M91x_USE(Y) || M91x_USE(Y2))
+ #define M91x_SOME_Z (M91x_USE(Z) || M91x_USE(Z2) || M91x_USE(Z3) || M91x_USE(Z4))
+ #define M91x_SOME_E (M91x_USE_E(0) || M91x_USE_E(1) || M91x_USE_E(2) || M91x_USE_E(3) || M91x_USE_E(4) || M91x_USE_E(5) || M91x_USE_E(6) || M91x_USE_E(7))
+
+ #if !M91x_SOME_X && !M91x_SOME_Y && !M91x_SOME_Z && !M91x_SOME_E
+ #error "MONITOR_DRIVER_STATUS requires at least one TMC2130, 2160, 2208, 2209, 2660, 5130, or 5160."
+ #endif
+
+ /**
+ * M911: Report TMC stepper driver overtemperature pre-warn flag
+ * This flag is held by the library, persisting until cleared by M912
+ */
+ void GcodeSuite::M911() {
+ #if M91x_USE(X)
+ tmc_report_otpw(stepperX);
+ #endif
+ #if M91x_USE(X2)
+ tmc_report_otpw(stepperX2);
+ #endif
+ #if M91x_USE(Y)
+ tmc_report_otpw(stepperY);
+ #endif
+ #if M91x_USE(Y2)
+ tmc_report_otpw(stepperY2);
+ #endif
+ #if M91x_USE(Z)
+ tmc_report_otpw(stepperZ);
+ #endif
+ #if M91x_USE(Z2)
+ tmc_report_otpw(stepperZ2);
+ #endif
+ #if M91x_USE(Z3)
+ tmc_report_otpw(stepperZ3);
+ #endif
+ #if M91x_USE(Z4)
+ tmc_report_otpw(stepperZ4);
+ #endif
+ #if M91x_USE_E(0)
+ tmc_report_otpw(stepperE0);
+ #endif
+ #if M91x_USE_E(1)
+ tmc_report_otpw(stepperE1);
+ #endif
+ #if M91x_USE_E(2)
+ tmc_report_otpw(stepperE2);
+ #endif
+ #if M91x_USE_E(3)
+ tmc_report_otpw(stepperE3);
+ #endif
+ #if M91x_USE_E(4)
+ tmc_report_otpw(stepperE4);
+ #endif
+ #if M91x_USE_E(5)
+ tmc_report_otpw(stepperE5);
+ #endif
+ #if M91x_USE_E(6)
+ tmc_report_otpw(stepperE6);
+ #endif
+ #if M91x_USE_E(7)
+ tmc_report_otpw(stepperE7);
+ #endif
+ }
+
+ /**
+ * M912: Clear TMC stepper driver overtemperature pre-warn flag held by the library
+ * Specify one or more axes with X, Y, Z, X1, Y1, Z1, X2, Y2, Z2, Z3, Z4 and E[index].
+ * If no axes are given, clear all.
+ *
+ * Examples:
+ * M912 X ; clear X and X2
+ * M912 X1 ; clear X1 only
+ * M912 X2 ; clear X2 only
+ * M912 X E ; clear X, X2, and all E
+ * M912 E1 ; clear E1 only
+ */
+ void GcodeSuite::M912() {
+ #if M91x_SOME_X
+ const bool hasX = parser.seen(axis_codes.x);
+ #else
+ constexpr bool hasX = false;
+ #endif
+
+ #if M91x_SOME_Y
+ const bool hasY = parser.seen(axis_codes.y);
+ #else
+ constexpr bool hasY = false;
+ #endif
+
+ #if M91x_SOME_Z
+ const bool hasZ = parser.seen(axis_codes.z);
+ #else
+ constexpr bool hasZ = false;
+ #endif
+
+ #if M91x_SOME_E
+ const bool hasE = parser.seen(axis_codes.e);
+ #else
+ constexpr bool hasE = false;
+ #endif
+
+ const bool hasNone = !hasX && !hasY && !hasZ && !hasE;
+
+ #if M91x_SOME_X
+ const int8_t xval = int8_t(parser.byteval(axis_codes.x, 0xFF));
+ #if M91x_USE(X)
+ if (hasNone || xval == 1 || (hasX && xval < 0)) tmc_clear_otpw(stepperX);
+ #endif
+ #if M91x_USE(X2)
+ if (hasNone || xval == 2 || (hasX && xval < 0)) tmc_clear_otpw(stepperX2);
+ #endif
+ #endif
+
+ #if M91x_SOME_Y
+ const int8_t yval = int8_t(parser.byteval(axis_codes.y, 0xFF));
+ #if M91x_USE(Y)
+ if (hasNone || yval == 1 || (hasY && yval < 0)) tmc_clear_otpw(stepperY);
+ #endif
+ #if M91x_USE(Y2)
+ if (hasNone || yval == 2 || (hasY && yval < 0)) tmc_clear_otpw(stepperY2);
+ #endif
+ #endif
+
+ #if M91x_SOME_Z
+ const int8_t zval = int8_t(parser.byteval(axis_codes.z, 0xFF));
+ #if M91x_USE(Z)
+ if (hasNone || zval == 1 || (hasZ && zval < 0)) tmc_clear_otpw(stepperZ);
+ #endif
+ #if M91x_USE(Z2)
+ if (hasNone || zval == 2 || (hasZ && zval < 0)) tmc_clear_otpw(stepperZ2);
+ #endif
+ #if M91x_USE(Z3)
+ if (hasNone || zval == 3 || (hasZ && zval < 0)) tmc_clear_otpw(stepperZ3);
+ #endif
+ #if M91x_USE(Z4)
+ if (hasNone || zval == 4 || (hasZ && zval < 0)) tmc_clear_otpw(stepperZ4);
+ #endif
+ #endif
+
+ #if M91x_SOME_E
+ const int8_t eval = int8_t(parser.byteval(axis_codes.e, 0xFF));
+ #if M91x_USE_E(0)
+ if (hasNone || eval == 0 || (hasE && eval < 0)) tmc_clear_otpw(stepperE0);
+ #endif
+ #if M91x_USE_E(1)
+ if (hasNone || eval == 1 || (hasE && eval < 0)) tmc_clear_otpw(stepperE1);
+ #endif
+ #if M91x_USE_E(2)
+ if (hasNone || eval == 2 || (hasE && eval < 0)) tmc_clear_otpw(stepperE2);
+ #endif
+ #if M91x_USE_E(3)
+ if (hasNone || eval == 3 || (hasE && eval < 0)) tmc_clear_otpw(stepperE3);
+ #endif
+ #if M91x_USE_E(4)
+ if (hasNone || eval == 4 || (hasE && eval < 0)) tmc_clear_otpw(stepperE4);
+ #endif
+ #if M91x_USE_E(5)
+ if (hasNone || eval == 5 || (hasE && eval < 0)) tmc_clear_otpw(stepperE5);
+ #endif
+ #if M91x_USE_E(6)
+ if (hasNone || eval == 6 || (hasE && eval < 0)) tmc_clear_otpw(stepperE6);
+ #endif
+ #if M91x_USE_E(7)
+ if (hasNone || eval == 7 || (hasE && eval < 0)) tmc_clear_otpw(stepperE7);
+ #endif
+ #endif
+ }
+
+#endif // MONITOR_DRIVER_STATUS
+
+/**
+ * M913: Set HYBRID_THRESHOLD speed.
+ */
+#if ENABLED(HYBRID_THRESHOLD)
+ void GcodeSuite::M913() {
+ #define TMC_SAY_PWMTHRS(A,Q) tmc_print_pwmthrs(stepper##Q)
+ #define TMC_SET_PWMTHRS(A,Q) stepper##Q.set_pwm_thrs(value)
+ #define TMC_SAY_PWMTHRS_E(E) tmc_print_pwmthrs(stepperE##E)
+ #define TMC_SET_PWMTHRS_E(E) stepperE##E.set_pwm_thrs(value)
+
+ bool report = true;
+ #if AXIS_IS_TMC(X) || AXIS_IS_TMC(X2) || AXIS_IS_TMC(Y) || AXIS_IS_TMC(Y2) || AXIS_IS_TMC(Z) || AXIS_IS_TMC(Z2) || AXIS_IS_TMC(Z3) || AXIS_IS_TMC(Z4)
+ const uint8_t index = parser.byteval('I');
+ #endif
+ LOOP_XYZE(i) if (int32_t value = parser.longval(axis_codes[i])) {
+ report = false;
+ switch (i) {
+ case X_AXIS:
+ #if AXIS_HAS_STEALTHCHOP(X)
+ if (index < 2) TMC_SET_PWMTHRS(X,X);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(X2)
+ if (!(index & 1)) TMC_SET_PWMTHRS(X,X2);
+ #endif
+ break;
+ case Y_AXIS:
+ #if AXIS_HAS_STEALTHCHOP(Y)
+ if (index < 2) TMC_SET_PWMTHRS(Y,Y);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y2)
+ if (!(index & 1)) TMC_SET_PWMTHRS(Y,Y2);
+ #endif
+ break;
+ case Z_AXIS:
+ #if AXIS_HAS_STEALTHCHOP(Z)
+ if (index < 2) TMC_SET_PWMTHRS(Z,Z);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z2)
+ if (index == 0 || index == 2) TMC_SET_PWMTHRS(Z,Z2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z3)
+ if (index == 0 || index == 3) TMC_SET_PWMTHRS(Z,Z3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z4)
+ if (index == 0 || index == 4) TMC_SET_PWMTHRS(Z,Z4);
+ #endif
+ break;
+ case E_AXIS: {
+ #if E_STEPPERS
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ switch (target_extruder) {
+ #if AXIS_HAS_STEALTHCHOP(E0)
+ case 0: TMC_SET_PWMTHRS_E(0); break;
+ #endif
+ #if E_STEPPERS > 1 && AXIS_HAS_STEALTHCHOP(E1)
+ case 1: TMC_SET_PWMTHRS_E(1); break;
+ #endif
+ #if E_STEPPERS > 2 && AXIS_HAS_STEALTHCHOP(E2)
+ case 2: TMC_SET_PWMTHRS_E(2); break;
+ #endif
+ #if E_STEPPERS > 3 && AXIS_HAS_STEALTHCHOP(E3)
+ case 3: TMC_SET_PWMTHRS_E(3); break;
+ #endif
+ #if E_STEPPERS > 4 && AXIS_HAS_STEALTHCHOP(E4)
+ case 4: TMC_SET_PWMTHRS_E(4); break;
+ #endif
+ #if E_STEPPERS > 5 && AXIS_HAS_STEALTHCHOP(E5)
+ case 5: TMC_SET_PWMTHRS_E(5); break;
+ #endif
+ #if E_STEPPERS > 6 && AXIS_HAS_STEALTHCHOP(E6)
+ case 6: TMC_SET_PWMTHRS_E(6); break;
+ #endif
+ #if E_STEPPERS > 7 && AXIS_HAS_STEALTHCHOP(E7)
+ case 7: TMC_SET_PWMTHRS_E(7); break;
+ #endif
+ }
+ #endif // E_STEPPERS
+ } break;
+ }
+ }
+
+ if (report) {
+ #if AXIS_HAS_STEALTHCHOP(X)
+ TMC_SAY_PWMTHRS(X,X);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(X2)
+ TMC_SAY_PWMTHRS(X,X2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y)
+ TMC_SAY_PWMTHRS(Y,Y);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Y2)
+ TMC_SAY_PWMTHRS(Y,Y2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z)
+ TMC_SAY_PWMTHRS(Z,Z);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z2)
+ TMC_SAY_PWMTHRS(Z,Z2);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z3)
+ TMC_SAY_PWMTHRS(Z,Z3);
+ #endif
+ #if AXIS_HAS_STEALTHCHOP(Z4)
+ TMC_SAY_PWMTHRS(Z,Z4);
+ #endif
+ #if E_STEPPERS && AXIS_HAS_STEALTHCHOP(E0)
+ TMC_SAY_PWMTHRS_E(0);
+ #endif
+ #if E_STEPPERS > 1 && AXIS_HAS_STEALTHCHOP(E1)
+ TMC_SAY_PWMTHRS_E(1);
+ #endif
+ #if E_STEPPERS > 2 && AXIS_HAS_STEALTHCHOP(E2)
+ TMC_SAY_PWMTHRS_E(2);
+ #endif
+ #if E_STEPPERS > 3 && AXIS_HAS_STEALTHCHOP(E3)
+ TMC_SAY_PWMTHRS_E(3);
+ #endif
+ #if E_STEPPERS > 4 && AXIS_HAS_STEALTHCHOP(E4)
+ TMC_SAY_PWMTHRS_E(4);
+ #endif
+ #if E_STEPPERS > 5 && AXIS_HAS_STEALTHCHOP(E5)
+ TMC_SAY_PWMTHRS_E(5);
+ #endif
+ #if E_STEPPERS > 6 && AXIS_HAS_STEALTHCHOP(E6)
+ TMC_SAY_PWMTHRS_E(6);
+ #endif
+ #if E_STEPPERS > 7 && AXIS_HAS_STEALTHCHOP(E7)
+ TMC_SAY_PWMTHRS_E(7);
+ #endif
+ }
+ }
+#endif // HYBRID_THRESHOLD
+
+/**
+ * M914: Set StallGuard sensitivity.
+ */
+#if USE_SENSORLESS
+ void GcodeSuite::M914() {
+
+ bool report = true;
+ const uint8_t index = parser.byteval('I');
+ LOOP_XYZ(i) if (parser.seen(XYZ_CHAR(i))) {
+ const int16_t value = parser.value_int();
+ report = false;
+ switch (i) {
+ #if X_SENSORLESS
+ case X_AXIS:
+ #if AXIS_HAS_STALLGUARD(X)
+ if (index < 2) stepperX.homing_threshold(value);
+ #endif
+ #if AXIS_HAS_STALLGUARD(X2)
+ if (!(index & 1)) stepperX2.homing_threshold(value);
+ #endif
+ break;
+ #endif
+ #if Y_SENSORLESS
+ case Y_AXIS:
+ #if AXIS_HAS_STALLGUARD(Y)
+ if (index < 2) stepperY.homing_threshold(value);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Y2)
+ if (!(index & 1)) stepperY2.homing_threshold(value);
+ #endif
+ break;
+ #endif
+ #if Z_SENSORLESS
+ case Z_AXIS:
+ #if AXIS_HAS_STALLGUARD(Z)
+ if (index < 2) stepperZ.homing_threshold(value);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Z2)
+ if (index == 0 || index == 2) stepperZ2.homing_threshold(value);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Z3)
+ if (index == 0 || index == 3) stepperZ3.homing_threshold(value);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Z4)
+ if (index == 0 || index == 4) stepperZ4.homing_threshold(value);
+ #endif
+ break;
+ #endif
+ }
+ }
+
+ if (report) {
+ #if X_SENSORLESS
+ #if AXIS_HAS_STALLGUARD(X)
+ tmc_print_sgt(stepperX);
+ #endif
+ #if AXIS_HAS_STALLGUARD(X2)
+ tmc_print_sgt(stepperX2);
+ #endif
+ #endif
+ #if Y_SENSORLESS
+ #if AXIS_HAS_STALLGUARD(Y)
+ tmc_print_sgt(stepperY);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Y2)
+ tmc_print_sgt(stepperY2);
+ #endif
+ #endif
+ #if Z_SENSORLESS
+ #if AXIS_HAS_STALLGUARD(Z)
+ tmc_print_sgt(stepperZ);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Z2)
+ tmc_print_sgt(stepperZ2);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Z3)
+ tmc_print_sgt(stepperZ3);
+ #endif
+ #if AXIS_HAS_STALLGUARD(Z4)
+ tmc_print_sgt(stepperZ4);
+ #endif
+ #endif
+ }
+ }
+#endif // USE_SENSORLESS
+
+#endif // HAS_TRINAMIC_CONFIG
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
new file mode 100644
index 0000000..f917318
--- /dev/null
+++ b/Marlin/src/gcode/gcode.cpp
@@ -0,0 +1,1084 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * gcode.cpp - Temporary container for all gcode handlers
+ * Most will migrate to classes, by feature.
+ */
+
+#include "gcode.h"
+GcodeSuite gcode;
+
+#if ENABLED(WIFI_CUSTOM_COMMAND)
+ extern bool wifi_custom_command(char * const command_ptr);
+#endif
+
+#include "parser.h"
+#include "queue.h"
+#include "../module/motion.h"
+
+#if ENABLED(PRINTCOUNTER)
+ #include "../module/printcounter.h"
+#endif
+
+#if ENABLED(HOST_ACTION_COMMANDS)
+ #include "../feature/host_actions.h"
+#endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ #include "../sd/cardreader.h"
+ #include "../feature/powerloss.h"
+#endif
+
+#if ENABLED(CANCEL_OBJECTS)
+ #include "../feature/cancel_object.h"
+#endif
+
+#if ENABLED(LASER_MOVE_POWER)
+ #include "../feature/spindle_laser.h"
+#endif
+
+#if ENABLED(PASSWORD_FEATURE)
+ #include "../feature/password/password.h"
+#endif
+
+#include "../MarlinCore.h" // for idle, kill
+
+// Inactivity shutdown
+millis_t GcodeSuite::previous_move_ms = 0,
+ GcodeSuite::max_inactive_time = 0,
+ GcodeSuite::stepper_inactive_time = SEC_TO_MS(DEFAULT_STEPPER_DEACTIVE_TIME);
+
+// Relative motion mode for each logical axis
+static constexpr xyze_bool_t ar_init = AXIS_RELATIVE_MODES;
+uint8_t GcodeSuite::axis_relative = (
+ (ar_init.x ? _BV(REL_X) : 0)
+ | (ar_init.y ? _BV(REL_Y) : 0)
+ | (ar_init.z ? _BV(REL_Z) : 0)
+ | (ar_init.e ? _BV(REL_E) : 0)
+);
+
+#if EITHER(HAS_AUTO_REPORTING, HOST_KEEPALIVE_FEATURE)
+ bool GcodeSuite::autoreport_paused; // = false
+#endif
+
+#if ENABLED(HOST_KEEPALIVE_FEATURE)
+ GcodeSuite::MarlinBusyState GcodeSuite::busy_state = NOT_BUSY;
+ uint8_t GcodeSuite::host_keepalive_interval = DEFAULT_KEEPALIVE_INTERVAL;
+#endif
+
+#if ENABLED(CNC_WORKSPACE_PLANES)
+ GcodeSuite::WorkspacePlane GcodeSuite::workspace_plane = PLANE_XY;
+#endif
+
+#if ENABLED(CNC_COORDINATE_SYSTEMS)
+ int8_t GcodeSuite::active_coordinate_system = -1; // machine space
+ xyz_pos_t GcodeSuite::coordinate_system[MAX_COORDINATE_SYSTEMS];
+#endif
+
+/**
+ * Get the target extruder from the T parameter or the active_extruder
+ * Return -1 if the T parameter is out of range
+ */
+int8_t GcodeSuite::get_target_extruder_from_command() {
+ if (parser.seenval('T')) {
+ const int8_t e = parser.value_byte();
+ if (e < EXTRUDERS) return e;
+ SERIAL_ECHO_START();
+ SERIAL_CHAR('M'); SERIAL_ECHO(parser.codenum);
+ SERIAL_ECHOLNPAIR(" " STR_INVALID_EXTRUDER " ", int(e));
+ return -1;
+ }
+ return active_extruder;
+}
+
+/**
+ * Get the target e stepper from the T parameter
+ * Return -1 if the T parameter is out of range or unspecified
+ */
+int8_t GcodeSuite::get_target_e_stepper_from_command() {
+ const int8_t e = parser.intval('T', -1);
+ if (WITHIN(e, 0, E_STEPPERS - 1)) return e;
+
+ SERIAL_ECHO_START();
+ SERIAL_CHAR('M'); SERIAL_ECHO(parser.codenum);
+ if (e == -1)
+ SERIAL_ECHOLNPGM(" " STR_E_STEPPER_NOT_SPECIFIED);
+ else
+ SERIAL_ECHOLNPAIR(" " STR_INVALID_E_STEPPER " ", int(e));
+ return -1;
+}
+
+/**
+ * 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 GcodeSuite::get_destination_from_command() {
+ xyze_bool_t seen = { false, false, false, false };
+
+ #if ENABLED(CANCEL_OBJECTS)
+ const bool &skip_move = cancelable.skipping;
+ #else
+ constexpr bool skip_move = false;
+ #endif
+
+ // Get new XYZ position, whether absolute or relative
+ LOOP_XYZ(i) {
+ if ( (seen[i] = parser.seenval(XYZ_CHAR(i))) ) {
+ const float v = parser.value_axis_units((AxisEnum)i);
+ if (skip_move)
+ destination[i] = current_position[i];
+ else
+ destination[i] = axis_is_relative(AxisEnum(i)) ? current_position[i] + v : LOGICAL_TO_NATIVE(v, i);
+ }
+ else
+ destination[i] = current_position[i];
+ }
+
+ // Get new E position, whether absolute or relative
+ if ( (seen.e = parser.seenval('E')) ) {
+ const float v = parser.value_axis_units(E_AXIS);
+ destination.e = axis_is_relative(E_AXIS) ? current_position.e + v : v;
+ }
+ else
+ destination.e = current_position.e;
+
+ #if ENABLED(POWER_LOSS_RECOVERY) && !PIN_EXISTS(POWER_LOSS)
+ // Only update power loss recovery on moves with E
+ if (recovery.enabled && IS_SD_PRINTING() && seen.e && (seen.x || seen.y))
+ recovery.save();
+ #endif
+
+ if (parser.linearval('F') > 0)
+ feedrate_mm_s = parser.value_feedrate();
+
+ #if ENABLED(PRINTCOUNTER)
+ if (!DEBUGGING(DRYRUN) && !skip_move)
+ print_job_timer.incFilamentUsed(destination.e - current_position.e);
+ #endif
+
+ // Get ABCDHI mixing factors
+ #if BOTH(MIXING_EXTRUDER, DIRECT_MIXING_IN_G1)
+ M165();
+ #endif
+
+ #if ENABLED(LASER_MOVE_POWER)
+ // Set the laser power in the planner to configure this move
+ if (parser.seen('S')) {
+ const float spwr = parser.value_float();
+ cutter.inline_power(TERN(SPINDLE_LASER_PWM, cutter.power_to_range(cutter_power_t(round(spwr))), spwr > 0 ? 255 : 0));
+ }
+ else if (ENABLED(LASER_MOVE_G0_OFF) && parser.codenum == 0) // G0
+ cutter.set_inline_enabled(false);
+ #endif
+}
+
+/**
+ * Dwell waits immediately. It does not synchronize. Use M400 instead of G4
+ */
+void GcodeSuite::dwell(millis_t time) {
+ time += millis();
+ while (PENDING(millis(), time)) idle();
+}
+
+/**
+ * When G29_RETRY_AND_RECOVER is enabled, call G29() in
+ * a loop with recovery and retry handling.
+ */
+#if BOTH(HAS_LEVELING, G29_RETRY_AND_RECOVER)
+
+ void GcodeSuite::event_probe_recover() {
+ TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_INFO, PSTR("G29 Retrying"), DISMISS_STR));
+ #ifdef ACTION_ON_G29_RECOVER
+ host_action(PSTR(ACTION_ON_G29_RECOVER));
+ #endif
+ #ifdef G29_RECOVER_COMMANDS
+ process_subcommands_now_P(PSTR(G29_RECOVER_COMMANDS));
+ #endif
+ }
+
+ void GcodeSuite::event_probe_failure() {
+ #ifdef ACTION_ON_G29_FAILURE
+ host_action(PSTR(ACTION_ON_G29_FAILURE));
+ #endif
+ #ifdef G29_FAILURE_COMMANDS
+ process_subcommands_now_P(PSTR(G29_FAILURE_COMMANDS));
+ #endif
+ #if ENABLED(G29_HALT_ON_FAILURE)
+ #ifdef ACTION_ON_CANCEL
+ host_action_cancel();
+ #endif
+ kill(GET_TEXT(MSG_LCD_PROBING_FAILED));
+ #endif
+ }
+
+ #ifndef G29_MAX_RETRIES
+ #define G29_MAX_RETRIES 0
+ #endif
+
+ void GcodeSuite::G29_with_retry() {
+ uint8_t retries = G29_MAX_RETRIES;
+ while (G29()) { // G29 should return true for failed probes ONLY
+ if (retries) {
+ event_probe_recover();
+ --retries;
+ }
+ else {
+ event_probe_failure();
+ return;
+ }
+ }
+
+ TERN_(HOST_PROMPT_SUPPORT, host_action_prompt_end());
+
+ #ifdef G29_SUCCESS_COMMANDS
+ process_subcommands_now_P(PSTR(G29_SUCCESS_COMMANDS));
+ #endif
+ }
+
+#endif // HAS_LEVELING && G29_RETRY_AND_RECOVER
+
+//
+// Placeholders for non-migrated codes
+//
+#if ENABLED(M100_FREE_MEMORY_WATCHER)
+ extern void M100_dump_routine(PGM_P const title, const char * const start, const char * const end);
+#endif
+
+/**
+ * Process the parsed command and dispatch it to its handler
+ */
+void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
+ KEEPALIVE_STATE(IN_HANDLER);
+
+ /**
+ * Block all Gcodes except M511 Unlock Printer, if printer is locked
+ * Will still block Gcodes if M511 is disabled, in which case the printer should be unlocked via LCD Menu
+ */
+ #if ENABLED(PASSWORD_FEATURE)
+ if (password.is_locked && !parser.is_command('M', 511)) {
+ SERIAL_ECHO_MSG(STR_PRINTER_LOCKED);
+ if (!no_ok) queue.ok_to_send();
+ return;
+ }
+ #endif
+
+ // Handle a known G, M, or T
+ switch (parser.command_letter) {
+ case 'G': switch (parser.codenum) {
+
+ case 0: case 1: // G0: Fast Move, G1: Linear Move
+ G0_G1(TERN_(HAS_FAST_MOVES, parser.codenum == 0)); break;
+
+ #if ENABLED(ARC_SUPPORT) && DISABLED(SCARA)
+ case 2: case 3: G2_G3(parser.codenum == 2); break; // G2: CW ARC, G3: CCW ARC
+ #endif
+
+ case 4: G4(); break; // G4: Dwell
+
+ #if ENABLED(BEZIER_CURVE_SUPPORT)
+ case 5: G5(); break; // G5: Cubic B_spline
+ #endif
+
+ #if ENABLED(DIRECT_STEPPING)
+ case 6: G6(); break; // G6: Direct Stepper Move
+ #endif
+
+ #if ENABLED(FWRETRACT)
+ case 10: G10(); break; // G10: Retract / Swap Retract
+ case 11: G11(); break; // G11: Recover / Swap Recover
+ #endif
+
+ #if ENABLED(NOZZLE_CLEAN_FEATURE)
+ case 12: G12(); break; // G12: Nozzle Clean
+ #endif
+
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ case 17: G17(); break; // G17: Select Plane XY
+ case 18: G18(); break; // G18: Select Plane ZX
+ case 19: G19(); break; // G19: Select Plane YZ
+ #endif
+
+ #if ENABLED(INCH_MODE_SUPPORT)
+ case 20: G20(); break; // G20: Inch Mode
+ case 21: G21(); break; // G21: MM Mode
+ #else
+ case 21: NOOP; break; // No error on unknown G21
+ #endif
+
+ #if ENABLED(G26_MESH_VALIDATION)
+ case 26: G26(); break; // G26: Mesh Validation Pattern generation
+ #endif
+
+ #if ENABLED(NOZZLE_PARK_FEATURE)
+ case 27: G27(); break; // G27: Nozzle Park
+ #endif
+
+ case 28: G28(); break; // G28: Home one or more axes
+
+ #if HAS_LEVELING
+ case 29: // G29: Bed leveling calibration
+ TERN(G29_RETRY_AND_RECOVER, G29_with_retry, G29)();
+ break;
+ #endif
+
+ #if HAS_BED_PROBE
+ case 30: G30(); break; // G30: Single Z probe
+ #if ENABLED(Z_PROBE_SLED)
+ case 31: G31(); break; // G31: dock the sled
+ case 32: G32(); break; // G32: undock the sled
+ #endif
+ #endif
+
+ #if ENABLED(DELTA_AUTO_CALIBRATION)
+ case 33: G33(); break; // G33: Delta Auto-Calibration
+ #endif
+
+ #if ANY(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN, MECHANICAL_GANTRY_CALIBRATION)
+ case 34: G34(); break; // G34: Z Stepper automatic alignment using probe
+ #endif
+
+ #if ENABLED(ASSISTED_TRAMMING)
+ case 35: G35(); break; // G35: Read four bed corners to help adjust bed screws
+ #endif
+
+ #if ENABLED(G38_PROBE_TARGET)
+ case 38: // G38.2, G38.3: Probe towards target
+ if (WITHIN(parser.subcode, 2, TERN(G38_PROBE_AWAY, 5, 3)))
+ G38(parser.subcode); // G38.4, G38.5: Probe away from target
+ break;
+ #endif
+
+ #if ENABLED(CNC_COORDINATE_SYSTEMS)
+ case 53: G53(); break; // G53: (prefix) Apply native workspace
+ case 54: G54(); break; // G54: Switch to Workspace 1
+ case 55: G55(); break; // G55: Switch to Workspace 2
+ case 56: G56(); break; // G56: Switch to Workspace 3
+ case 57: G57(); break; // G57: Switch to Workspace 4
+ case 58: G58(); break; // G58: Switch to Workspace 5
+ case 59: G59(); break; // G59.0 - G59.3: Switch to Workspace 6-9
+ #endif
+
+ #if SAVED_POSITIONS
+ case 60: G60(); break; // G60: save current position
+ case 61: G61(); break; // G61: Apply/restore saved coordinates.
+ #endif
+
+ #if ENABLED(PROBE_TEMP_COMPENSATION)
+ case 76: G76(); break; // G76: Calibrate first layer compensation values
+ #endif
+
+ #if ENABLED(GCODE_MOTION_MODES)
+ case 80: G80(); break; // G80: Reset the current motion mode
+ #endif
+
+ case 90: set_relative_mode(false); break; // G90: Absolute Mode
+ case 91: set_relative_mode(true); break; // G91: Relative Mode
+
+ case 92: G92(); break; // G92: Set current axis position(s)
+
+ #if HAS_MESH
+ case 42: G42(); break; // G42: Coordinated move to a mesh point
+ #endif
+
+ #if ENABLED(CALIBRATION_GCODE)
+ case 425: G425(); break; // G425: Perform calibration with calibration cube
+ #endif
+
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ case 800: parser.debug(); break; // G800: GCode Parser Test for G
+ #endif
+
+ default: parser.unknown_command_warning(); break;
+ }
+ break;
+
+ case 'M': switch (parser.codenum) {
+
+ #if HAS_RESUME_CONTINUE
+ case 0: // M0: Unconditional stop - Wait for user button press on LCD
+ case 1: M0_M1(); break; // M1: Conditional stop - Wait for user button press on LCD
+ #endif
+
+ #if HAS_CUTTER
+ case 3: M3_M4(false); break; // M3: Turn ON Laser | Spindle (clockwise), set Power | Speed
+ case 4: M3_M4(true ); break; // M4: Turn ON Laser | Spindle (counter-clockwise), set Power | Speed
+ case 5: M5(); break; // M5: Turn OFF Laser | Spindle
+ #endif
+
+ #if ENABLED(COOLANT_CONTROL)
+ #if ENABLED(COOLANT_MIST)
+ case 7: M7(); break; // M7: Mist coolant ON
+ #endif
+ #if ENABLED(COOLANT_FLOOD)
+ case 8: M8(); break; // M8: Flood coolant ON
+ #endif
+ case 9: M9(); break; // M9: Coolant OFF
+ #endif
+
+ #if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER)
+ case 12: M12(); break; // M12: Synchronize and optionally force a CLC set
+ #endif
+
+ #if ENABLED(EXPECTED_PRINTER_CHECK)
+ case 16: M16(); break; // M16: Expected printer check
+ #endif
+
+ case 17: M17(); break; // M17: Enable all stepper motors
+
+ #if ENABLED(SDSUPPORT)
+ case 20: M20(); break; // M20: List SD card
+ case 21: M21(); break; // M21: Init SD card
+ case 22: M22(); break; // M22: Release SD card
+ case 23: M23(); break; // M23: Select file
+ case 24: M24(); break; // M24: Start SD print
+ case 25: M25(); break; // M25: Pause SD print
+ case 26: M26(); break; // M26: Set SD index
+ case 27: M27(); break; // M27: Get SD status
+ case 28: M28(); break; // M28: Start SD write
+ case 29: M29(); break; // M29: Stop SD write
+ case 30: M30(); break; // M30 <filename> Delete File
+
+ #if HAS_MEDIA_SUBCALLS
+ case 32: M32(); break; // M32: Select file and start SD print
+ #endif
+
+ #if ENABLED(LONG_FILENAME_HOST_SUPPORT)
+ case 33: M33(); break; // M33: Get the long full path to a file or folder
+ #endif
+
+ #if BOTH(SDCARD_SORT_ALPHA, SDSORT_GCODE)
+ case 34: M34(); break; // M34: Set SD card sorting options
+ #endif
+
+ case 928: M928(); break; // M928: Start SD write
+ #endif // SDSUPPORT
+
+ case 31: M31(); break; // M31: Report time since the start of SD print or last M109
+
+ #if ENABLED(DIRECT_PIN_CONTROL)
+ case 42: M42(); break; // M42: Change pin state
+ #endif
+
+ #if ENABLED(PINS_DEBUGGING)
+ case 43: M43(); break; // M43: Read pin state
+ #endif
+
+ #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
+ case 48: M48(); break; // M48: Z probe repeatability test
+ #endif
+
+ #if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+ case 73: M73(); break; // M73: Set progress percentage (for display on LCD)
+ #endif
+
+ case 75: M75(); break; // M75: Start print timer
+ case 76: M76(); break; // M76: Pause print timer
+ case 77: M77(); break; // M77: Stop print timer
+
+ #if ENABLED(PRINTCOUNTER)
+ case 78: M78(); break; // M78: Show print statistics
+ #endif
+
+ #if ENABLED(M100_FREE_MEMORY_WATCHER)
+ case 100: M100(); break; // M100: Free Memory Report
+ #endif
+
+ #if EXTRUDERS
+ case 104: M104(); break; // M104: Set hot end temperature
+ case 109: M109(); break; // M109: Wait for hotend temperature to reach target
+ #endif
+
+ case 105: M105(); return; // M105: Report Temperatures (and say "ok")
+
+ #if HAS_FAN
+ case 106: M106(); break; // M106: Fan On
+ case 107: M107(); break; // M107: Fan Off
+ #endif
+
+ case 110: M110(); break; // M110: Set Current Line Number
+ case 111: M111(); break; // M111: Set debug level
+
+ #if DISABLED(EMERGENCY_PARSER)
+ case 108: M108(); break; // M108: Cancel Waiting
+ case 112: M112(); break; // M112: Full Shutdown
+ case 410: M410(); break; // M410: Quickstop - Abort all the planned moves.
+ TERN_(HOST_PROMPT_SUPPORT, case 876:) // M876: Handle Host prompt responses
+ #else
+ case 108: case 112: case 410:
+ TERN_(HOST_PROMPT_SUPPORT, case 876:)
+ break;
+ #endif
+
+ #if ENABLED(HOST_KEEPALIVE_FEATURE)
+ case 113: M113(); break; // M113: Set Host Keepalive interval
+ #endif
+
+ #if HAS_HEATED_BED
+ case 140: M140(); break; // M140: Set bed temperature
+ case 190: M190(); break; // M190: Wait for bed temperature to reach target
+ #endif
+
+ #if HAS_HEATED_CHAMBER
+ case 141: M141(); break; // M141: Set chamber temperature
+ case 191: M191(); break; // M191: Wait for chamber temperature to reach target
+ #endif
+
+ #if BOTH(AUTO_REPORT_TEMPERATURES, HAS_TEMP_SENSOR)
+ case 155: M155(); break; // M155: Set temperature auto-report interval
+ #endif
+
+ #if ENABLED(PARK_HEAD_ON_PAUSE)
+ case 125: M125(); break; // M125: Store current position and move to filament change position
+ #endif
+
+ #if ENABLED(BARICUDA)
+ // PWM for HEATER_1_PIN
+ #if HAS_HEATER_1
+ case 126: M126(); break; // M126: valve open
+ case 127: M127(); break; // M127: valve closed
+ #endif
+
+ // PWM for HEATER_2_PIN
+ #if HAS_HEATER_2
+ case 128: M128(); break; // M128: valve open
+ case 129: M129(); break; // M129: valve closed
+ #endif
+ #endif // BARICUDA
+
+ #if ENABLED(PSU_CONTROL)
+ case 80: M80(); break; // M80: Turn on Power Supply
+ #endif
+ case 81: M81(); break; // M81: Turn off Power, including Power Supply, if possible
+
+ case 82: M82(); break; // M82: Set E axis normal mode (same as other axes)
+ case 83: M83(); break; // M83: Set E axis relative mode
+ case 18: case 84: M18_M84(); break; // M18/M84: Disable Steppers / Set Timeout
+ case 85: M85(); break; // M85: Set inactivity stepper shutdown timeout
+ case 92: M92(); break; // M92: Set the steps-per-unit for one or more axes
+ case 114: M114(); break; // M114: Report current position
+ case 115: M115(); break; // M115: Report capabilities
+ case 117: M117(); break; // M117: Set LCD message text, if possible
+ case 118: M118(); break; // M118: Display a message in the host console
+ case 119: M119(); break; // M119: Report endstop states
+ case 120: M120(); break; // M120: Enable endstops
+ case 121: M121(); break; // M121: Disable endstops
+
+ #if PREHEAT_COUNT
+ case 145: M145(); break; // M145: Set material heatup parameters
+ #endif
+
+ #if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+ case 149: M149(); break; // M149: Set temperature units
+ #endif
+
+ #if HAS_COLOR_LEDS
+ case 150: M150(); break; // M150: Set Status LED Color
+ #endif
+
+ #if ENABLED(MIXING_EXTRUDER)
+ case 163: M163(); break; // M163: Set a component weight for mixing extruder
+ case 164: M164(); break; // M164: Save current mix as a virtual extruder
+ #if ENABLED(DIRECT_MIXING_IN_G1)
+ case 165: M165(); break; // M165: Set multiple mix weights
+ #endif
+ #if ENABLED(GRADIENT_MIX)
+ case 166: M166(); break; // M166: Set Gradient Mix
+ #endif
+ #endif
+
+ #if DISABLED(NO_VOLUMETRICS)
+ case 200: M200(); break; // M200: Set filament diameter, E to cubic units
+ #endif
+
+ case 201: M201(); break; // M201: Set max acceleration for print moves (units/s^2)
+
+ #if 0
+ case 202: M202(); break; // M202: Not used for Sprinter/grbl gen6
+ #endif
+
+ case 203: M203(); break; // M203: Set max feedrate (units/sec)
+ case 204: M204(); break; // M204: Set acceleration
+ case 205: M205(); break; // M205: Set advanced settings
+
+ #if HAS_M206_COMMAND
+ case 206: M206(); break; // M206: Set home offsets
+ #endif
+
+ #if ENABLED(FWRETRACT)
+ case 207: M207(); break; // M207: Set Retract Length, Feedrate, and Z lift
+ case 208: M208(); break; // M208: Set Recover (unretract) Additional Length and Feedrate
+ #if ENABLED(FWRETRACT_AUTORETRACT)
+ case 209:
+ if (MIN_AUTORETRACT <= MAX_AUTORETRACT) M209(); // M209: Turn Automatic Retract Detection on/off
+ break;
+ #endif
+ #endif
+
+ #if HAS_SOFTWARE_ENDSTOPS
+ case 211: M211(); break; // M211: Enable, Disable, and/or Report software endstops
+ #endif
+
+ #if HAS_MULTI_EXTRUDER
+ case 217: M217(); break; // M217: Set filament swap parameters
+ #endif
+
+ #if HAS_HOTEND_OFFSET
+ case 218: M218(); break; // M218: Set a tool offset
+ #endif
+
+ case 220: M220(); break; // M220: Set Feedrate Percentage: S<percent> ("FR" on your LCD)
+
+ #if EXTRUDERS
+ case 221: M221(); break; // M221: Set Flow Percentage
+ #endif
+
+ #if ENABLED(DIRECT_PIN_CONTROL)
+ case 226: M226(); break; // M226: Wait until a pin reaches a state
+ #endif
+
+ #if HAS_SERVOS
+ case 280: M280(); break; // M280: Set servo position absolute
+ #if ENABLED(EDITABLE_SERVO_ANGLES)
+ case 281: M281(); break; // M281: Set servo angles
+ #endif
+ #endif
+
+ #if ENABLED(BABYSTEPPING)
+ case 290: M290(); break; // M290: Babystepping
+ #endif
+
+ #if HAS_BUZZER
+ case 300: M300(); break; // M300: Play beep tone
+ #endif
+
+ #if ENABLED(PIDTEMP)
+ case 301: M301(); break; // M301: Set hotend PID parameters
+ #endif
+
+ #if ENABLED(PIDTEMPBED)
+ case 304: M304(); break; // M304: Set bed PID parameters
+ #endif
+
+ #if ENABLED(PHOTO_GCODE)
+ case 240: M240(); break; // M240: Trigger a camera
+ #endif
+
+ #if HAS_LCD_CONTRAST
+ case 250: M250(); break; // M250: Set LCD contrast
+ #endif
+
+ #if ENABLED(EXPERIMENTAL_I2CBUS)
+ case 260: M260(); break; // M260: Send data to an i2c slave
+ case 261: M261(); break; // M261: Request data from an i2c slave
+ #endif
+
+ #if ENABLED(PREVENT_COLD_EXTRUSION)
+ case 302: M302(); break; // M302: Allow cold extrudes (set the minimum extrude temperature)
+ #endif
+
+ #if HAS_PID_HEATING
+ case 303: M303(); break; // M303: PID autotune
+ #endif
+
+ #if HAS_USER_THERMISTORS
+ case 305: M305(); break; // M305: Set user thermistor parameters
+ #endif
+
+ #if ENABLED(REPETIER_GCODE_M360)
+ case 360: M360(); break; // M360: Firmware settings
+ #endif
+
+ #if ENABLED(MORGAN_SCARA)
+ case 360: if (M360()) return; break; // M360: SCARA Theta pos1
+ case 361: if (M361()) return; break; // M361: SCARA Theta pos2
+ case 362: if (M362()) return; break; // M362: SCARA Psi pos1
+ case 363: if (M363()) return; break; // M363: SCARA Psi pos2
+ case 364: if (M364()) return; break; // M364: SCARA Psi pos3 (90 deg to Theta)
+ #endif
+
+ #if EITHER(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL)
+ case 380: M380(); break; // M380: Activate solenoid on active (or specified) extruder
+ case 381: M381(); break; // M381: Disable all solenoids or, if MANUAL_SOLENOID_CONTROL, active (or specified) solenoid
+ #endif
+
+ case 400: M400(); break; // M400: Finish all moves
+
+ #if HAS_BED_PROBE
+ case 401: M401(); break; // M401: Deploy probe
+ case 402: M402(); break; // M402: Stow probe
+ #endif
+
+ #if HAS_PRUSA_MMU2
+ case 403: M403(); break;
+ #endif
+
+ #if ENABLED(FILAMENT_WIDTH_SENSOR)
+ case 404: M404(); break; // M404: Enter the nominal filament width (3mm, 1.75mm ) N<3.0> or display nominal filament width
+ case 405: M405(); break; // M405: Turn on filament sensor for control
+ case 406: M406(); break; // M406: Turn off filament sensor for control
+ case 407: M407(); break; // M407: Display measured filament diameter
+ #endif
+
+ #if HAS_FILAMENT_SENSOR
+ case 412: M412(); break; // M412: Enable/Disable filament runout detection
+ #endif
+
+ #if HAS_MULTI_LANGUAGE
+ case 414: M414(); break; // M414: Select multi language menu
+ #endif
+
+ #if HAS_LEVELING
+ case 420: M420(); break; // M420: Enable/Disable Bed Leveling
+ #endif
+
+ #if HAS_MESH
+ case 421: M421(); break; // M421: Set a Mesh Bed Leveling Z coordinate
+ #endif
+
+ #if ENABLED(BACKLASH_GCODE)
+ case 425: M425(); break; // M425: Tune backlash compensation
+ #endif
+
+ #if HAS_M206_COMMAND
+ case 428: M428(); break; // M428: Apply current_position to home_offset
+ #endif
+
+ #if HAS_POWER_MONITOR
+ case 430: M430(); break; // M430: Read the system current (A), voltage (V), and power (W)
+ #endif
+
+ #if ENABLED(CANCEL_OBJECTS)
+ case 486: M486(); break; // M486: Identify and cancel objects
+ #endif
+
+ case 500: M500(); break; // M500: Store settings in EEPROM
+ case 501: M501(); break; // M501: Read settings from EEPROM
+ case 502: M502(); break; // M502: Revert to default settings
+ #if DISABLED(DISABLE_M503)
+ case 503: M503(); break; // M503: print settings currently in memory
+ #endif
+ #if ENABLED(EEPROM_SETTINGS)
+ case 504: M504(); break; // M504: Validate EEPROM contents
+ #endif
+
+ #if ENABLED(PASSWORD_FEATURE)
+ case 510: M510(); break; // M510: Lock Printer
+ #if ENABLED(PASSWORD_UNLOCK_GCODE)
+ case 511: M511(); break; // M511: Unlock Printer
+ #endif
+ #if ENABLED(PASSWORD_CHANGE_GCODE)
+ case 512: M512(); break; // M512: Set/Change/Remove Password
+ #endif
+ #endif
+
+ #if ENABLED(SDSUPPORT)
+ case 524: M524(); break; // M524: Abort the current SD print job
+ #endif
+
+ #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT)
+ case 540: M540(); break; // M540: Set abort on endstop hit for SD printing
+ #endif
+
+ #if HAS_ETHERNET
+ case 552: M552(); break; // M552: Set IP address
+ case 553: M553(); break; // M553: Set gateway
+ case 554: M554(); break; // M554: Set netmask
+ #endif
+
+ #if ENABLED(BAUD_RATE_GCODE)
+ case 575: M575(); break; // M575: Set serial baudrate
+ #endif
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ case 600: M600(); break; // M600: Pause for Filament Change
+ case 603: M603(); break; // M603: Configure Filament Change
+ #endif
+
+ #if HAS_DUPLICATION_MODE
+ case 605: M605(); break; // M605: Set Dual X Carriage movement mode
+ #endif
+
+ #if ENABLED(DELTA)
+ case 665: M665(); break; // M665: Set delta configurations
+ #endif
+
+ #if ENABLED(DELTA) || HAS_EXTRA_ENDSTOPS
+ case 666: M666(); break; // M666: Set delta or multiple endstop adjustment
+ #endif
+
+ #if ENABLED(DUET_SMART_EFFECTOR) && PIN_EXISTS(SMART_EFFECTOR_MOD)
+ case 672: M672(); break; // M672: Set/clear Duet Smart Effector sensitivity
+ #endif
+
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ case 701: M701(); break; // M701: Load Filament
+ case 702: M702(); break; // M702: Unload Filament
+ #endif
+
+ #if ENABLED(CONTROLLER_FAN_EDITABLE)
+ case 710: M710(); break; // M710: Set Controller Fan settings
+ #endif
+
+ #if ENABLED(GCODE_MACROS)
+ case 810: case 811: case 812: case 813: case 814:
+ case 815: case 816: case 817: case 818: case 819:
+ M810_819(); break; // M810-M819: Define/execute G-code macro
+ #endif
+
+ #if HAS_BED_PROBE
+ case 851: M851(); break; // M851: Set Z Probe Z Offset
+ #endif
+
+ #if ENABLED(SKEW_CORRECTION_GCODE)
+ case 852: M852(); break; // M852: Set Skew factors
+ #endif
+
+ #if ENABLED(PROBE_TEMP_COMPENSATION)
+ case 192: M192(); break; // M192: Wait for probe temp
+ case 871: M871(); break; // M871: Print/reset/clear first layer temperature offset values
+ #endif
+
+ #if ENABLED(LIN_ADVANCE)
+ case 900: M900(); break; // M900: Set advance K factor.
+ #endif
+
+ #if ANY(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_PWM, HAS_MOTOR_CURRENT_I2C, HAS_MOTOR_CURRENT_DAC)
+ case 907: M907(); break; // M907: Set digital trimpot motor current using axis codes.
+ #if EITHER(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_DAC)
+ case 908: M908(); break; // M908: Control digital trimpot directly.
+ #if ENABLED(HAS_MOTOR_CURRENT_DAC)
+ case 909: M909(); break; // M909: Print digipot/DAC current value
+ case 910: M910(); break; // M910: Commit digipot/DAC value to external EEPROM
+ #endif
+ #endif
+ #endif
+
+ #if HAS_TRINAMIC_CONFIG
+ case 122: M122(); break; // M122: Report driver configuration and status
+ case 906: M906(); break; // M906: Set motor current in milliamps using axis codes X, Y, Z, E
+ #if HAS_STEALTHCHOP
+ case 569: M569(); break; // M569: Enable stealthChop on an axis.
+ #endif
+ #if ENABLED(MONITOR_DRIVER_STATUS)
+ case 911: M911(); break; // M911: Report TMC2130 prewarn triggered flags
+ case 912: M912(); break; // M912: Clear TMC2130 prewarn triggered flags
+ #endif
+ #if ENABLED(HYBRID_THRESHOLD)
+ case 913: M913(); break; // M913: Set HYBRID_THRESHOLD speed.
+ #endif
+ #if USE_SENSORLESS
+ case 914: M914(); break; // M914: Set StallGuard sensitivity.
+ #endif
+ #endif
+
+ #if HAS_L64XX
+ case 122: M122(); break; // M122: Report status
+ case 906: M906(); break; // M906: Set or get motor drive level
+ case 916: M916(); break; // M916: L6470 tuning: Increase drive level until thermal warning
+ case 917: M917(); break; // M917: L6470 tuning: Find minimum current thresholds
+ case 918: M918(); break; // M918: L6470 tuning: Increase speed until max or error
+ #endif
+
+ #if HAS_MICROSTEPS
+ case 350: M350(); break; // M350: Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers.
+ case 351: M351(); break; // M351: Toggle MS1 MS2 pins directly, S# determines MS1 or MS2, X# sets the pin high/low.
+ #endif
+
+ #if ENABLED(CASE_LIGHT_ENABLE)
+ case 355: M355(); break; // M355: Set case light brightness
+ #endif
+
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ case 800: parser.debug(); break; // M800: GCode Parser Test for M
+ #endif
+
+ #if ENABLED(GCODE_REPEAT_MARKERS)
+ case 808: M808(); break; // M808: Set / Goto repeat markers
+ #endif
+
+ #if ENABLED(I2C_POSITION_ENCODERS)
+ case 860: M860(); break; // M860: Report encoder module position
+ case 861: M861(); break; // M861: Report encoder module status
+ case 862: M862(); break; // M862: Perform axis test
+ case 863: M863(); break; // M863: Calibrate steps/mm
+ case 864: M864(); break; // M864: Change module address
+ case 865: M865(); break; // M865: Check module firmware version
+ case 866: M866(); break; // M866: Report axis error count
+ case 867: M867(); break; // M867: Toggle error correction
+ case 868: M868(); break; // M868: Set error correction threshold
+ case 869: M869(); break; // M869: Report axis error
+ #endif
+
+ #if ENABLED(MAGNETIC_PARKING_EXTRUDER)
+ case 951: M951(); break; // M951: Set Magnetic Parking Extruder parameters
+ #endif
+
+ #if ENABLED(Z_STEPPER_AUTO_ALIGN)
+ case 422: M422(); break; // M422: Set Z Stepper automatic alignment position using probe
+ #endif
+
+ #if ALL(HAS_SPI_FLASH, SDSUPPORT, MARLIN_DEV_MODE)
+ case 993: M993(); break; // M993: Backup SPI Flash to SD
+ case 994: M994(); break; // M994: Load a Backup from SD to SPI Flash
+ #endif
+
+ #if ENABLED(TOUCH_SCREEN_CALIBRATION)
+ case 995: M995(); break; // M995: Touch screen calibration for TFT display
+ #endif
+
+ #if ENABLED(PLATFORM_M997_SUPPORT)
+ case 997: M997(); break; // M997: Perform in-application firmware update
+ #endif
+
+ case 999: M999(); break; // M999: Restart after being Stopped
+
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ case 413: M413(); break; // M413: Enable/disable/query Power-Loss Recovery
+ case 1000: M1000(); break; // M1000: [INTERNAL] Resume from power-loss
+ #endif
+
+ #if ENABLED(SDSUPPORT)
+ case 1001: M1001(); break; // M1001: [INTERNAL] Handle SD completion
+ #endif
+
+ #if ENABLED(MAX7219_GCODE)
+ case 7219: M7219(); break; // M7219: Set LEDs, columns, and rows
+ #endif
+
+ default: parser.unknown_command_warning(); break;
+ }
+ break;
+
+ case 'T': T(parser.codenum); break; // Tn: Tool Change
+
+ #if ENABLED(MARLIN_DEV_MODE)
+ case 'D': D(parser.codenum); break; // Dn: Debug codes
+ #endif
+
+ default:
+ #if ENABLED(WIFI_CUSTOM_COMMAND)
+ if (wifi_custom_command(parser.command_ptr)) break;
+ #endif
+ parser.unknown_command_warning();
+ }
+
+ if (!no_ok) queue.ok_to_send();
+
+ SERIAL_OUT(msgDone); // Call the msgDone serial hook to signal command processing done
+}
+
+/**
+ * Process a single command and dispatch it to its handler
+ * This is called from the main loop()
+ */
+void GcodeSuite::process_next_command() {
+ char * const current_command = queue.command_buffer[queue.index_r];
+
+ PORT_REDIRECT(SERIAL_PORTMASK(queue.port[queue.index_r]));
+
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ recovery.queue_index_r = queue.index_r;
+ #endif
+
+ if (DEBUGGING(ECHO)) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLN(current_command);
+ #if ENABLED(M100_FREE_MEMORY_DUMPER)
+ SERIAL_ECHOPAIR("slot:", queue.index_r);
+ M100_dump_routine(PSTR(" Command Queue:"), &queue.command_buffer[0][0], &queue.command_buffer[BUFSIZE - 1][MAX_CMD_SIZE - 1]);
+ #endif
+ }
+
+ // Parse the next command in the queue
+ parser.parse(current_command);
+ process_parsed_command();
+}
+
+/**
+ * Run a series of commands, bypassing the command queue to allow
+ * G-code "macros" to be called from within other G-code handlers.
+ */
+
+void GcodeSuite::process_subcommands_now_P(PGM_P pgcode) {
+ char * const saved_cmd = parser.command_ptr; // Save the parser state
+ for (;;) {
+ PGM_P const delim = strchr_P(pgcode, '\n'); // Get address of next newline
+ const size_t len = delim ? delim - pgcode : strlen_P(pgcode); // Get the command length
+ char cmd[len + 1]; // Allocate a stack buffer
+ strncpy_P(cmd, pgcode, len); // Copy the command to the stack
+ cmd[len] = '\0'; // End with a nul
+ parser.parse(cmd); // Parse the command
+ process_parsed_command(true); // Process it
+ if (!delim) break; // Last command?
+ pgcode = delim + 1; // Get the next command
+ }
+ parser.parse(saved_cmd); // Restore the parser state
+}
+
+void GcodeSuite::process_subcommands_now(char * gcode) {
+ char * const saved_cmd = parser.command_ptr; // Save the parser state
+ for (;;) {
+ char * const delim = strchr(gcode, '\n'); // Get address of next newline
+ if (delim) *delim = '\0'; // Replace with nul
+ parser.parse(gcode); // Parse the current command
+ if (delim) *delim = '\n'; // Put back the newline
+ process_parsed_command(true); // Process it
+ if (!delim) break; // Last command?
+ gcode = delim + 1; // Get the next command
+ }
+ parser.parse(saved_cmd); // Restore the parser state
+}
+
+#if ENABLED(HOST_KEEPALIVE_FEATURE)
+
+ /**
+ * Output a "busy" message at regular intervals
+ * while the machine is not accepting commands.
+ */
+ void GcodeSuite::host_keepalive() {
+ const millis_t ms = millis();
+ static millis_t next_busy_signal_ms = 0;
+ if (!autoreport_paused && 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_MSG(STR_BUSY_PROCESSING);
+ break;
+ case PAUSED_FOR_USER:
+ SERIAL_ECHO_MSG(STR_BUSY_PAUSED_FOR_USER);
+ break;
+ case PAUSED_FOR_INPUT:
+ SERIAL_ECHO_MSG(STR_BUSY_PAUSED_FOR_INPUT);
+ break;
+ default:
+ break;
+ }
+ }
+ next_busy_signal_ms = ms + SEC_TO_MS(host_keepalive_interval);
+ }
+
+#endif // HOST_KEEPALIVE_FEATURE
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
new file mode 100644
index 0000000..7fd8d69
--- /dev/null
+++ b/Marlin/src/gcode/gcode.h
@@ -0,0 +1,906 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+/**
+ * gcode.h - Temporary container for all gcode handlers
+ */
+
+/**
+ * -----------------
+ * G-Codes in Marlin
+ * -----------------
+ *
+ * Helpful G-code references:
+ * - https://marlinfw.org/meta/gcode
+ * - https://reprap.org/wiki/G-code
+ * - https://linuxcnc.org/docs/html/gcode.html
+ *
+ * Help to document Marlin's G-codes online:
+ * - https://github.com/MarlinFirmware/MarlinDocumentation
+ *
+ * -----------------
+ *
+ * "G" Codes
+ *
+ * G0 -> G1
+ * G1 - Coordinated Movement X Y Z E
+ * G2 - CW ARC
+ * G3 - CCW ARC
+ * G4 - Dwell S<seconds> or P<milliseconds>
+ * G5 - Cubic B-spline with XYZE destination and IJPQ offsets
+ * G10 - Retract filament according to settings of M207 (Requires FWRETRACT)
+ * G11 - Retract recover filament according to settings of M208 (Requires FWRETRACT)
+ * G12 - Clean tool (Requires NOZZLE_CLEAN_FEATURE)
+ * G17 - Select Plane XY (Requires CNC_WORKSPACE_PLANES)
+ * G18 - Select Plane ZX (Requires CNC_WORKSPACE_PLANES)
+ * G19 - Select Plane YZ (Requires CNC_WORKSPACE_PLANES)
+ * G20 - Set input units to inches (Requires INCH_MODE_SUPPORT)
+ * G21 - Set input units to millimeters (Requires INCH_MODE_SUPPORT)
+ * G26 - Mesh Validation Pattern (Requires G26_MESH_VALIDATION)
+ * G27 - Park Nozzle (Requires NOZZLE_PARK_FEATURE)
+ * G28 - Home one or more axes
+ * G29 - Start or continue the bed leveling probe procedure (Requires bed leveling)
+ * G30 - Single Z probe, probes bed at X Y location (defaults to current XY location)
+ * G31 - Dock sled (Z_PROBE_SLED only)
+ * G32 - Undock sled (Z_PROBE_SLED only)
+ * G33 - Delta Auto-Calibration (Requires DELTA_AUTO_CALIBRATION)
+ * G34 - Z Stepper automatic alignment using probe: I<iterations> T<accuracy> A<amplification> (Requires Z_STEPPER_AUTO_ALIGN)
+ * G35 - Read bed corners to help adjust bed screws: T<screw_thread> (Requires ASSISTED_TRAMMING)
+ * G38 - Probe in any direction using the Z_MIN_PROBE (Requires G38_PROBE_TARGET)
+ * G42 - Coordinated move to a mesh point (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BLINEAR, or AUTO_BED_LEVELING_UBL)
+ * G60 - Save current position. (Requires SAVED_POSITIONS)
+ * G61 - Apply/restore saved coordinates. (Requires SAVED_POSITIONS)
+ * G76 - Calibrate first layer temperature offsets. (Requires PROBE_TEMP_COMPENSATION)
+ * G80 - Cancel current motion mode (Requires GCODE_MOTION_MODES)
+ * G90 - Use Absolute Coordinates
+ * G91 - Use Relative Coordinates
+ * G92 - Set current position to coordinates given
+ *
+ * "M" Codes
+ *
+ * M0 - Unconditional stop - Wait for user to press a button on the LCD (Only if ULTRA_LCD is enabled)
+ * M1 -> M0
+ * M3 - Turn ON Laser | Spindle (clockwise), set Power | Speed. (Requires SPINDLE_FEATURE or LASER_FEATURE)
+ * M4 - Turn ON Laser | Spindle (counter-clockwise), set Power | Speed. (Requires SPINDLE_FEATURE or LASER_FEATURE)
+ * M5 - Turn OFF Laser | Spindle. (Requires SPINDLE_FEATURE or LASER_FEATURE)
+ * M7 - Turn mist coolant ON. (Requires COOLANT_CONTROL)
+ * M8 - Turn flood coolant ON. (Requires COOLANT_CONTROL)
+ * M9 - Turn coolant OFF. (Requires COOLANT_CONTROL)
+ * M12 - Set up closed loop control system. (Requires EXTERNAL_CLOSED_LOOP_CONTROLLER)
+ * M16 - Expected printer check. (Requires EXPECTED_PRINTER_CHECK)
+ * M17 - Enable/Power all stepper motors
+ * M18 - Disable all stepper motors; same as M84
+ * 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<seconds>' 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<bytepos>] !/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<pin> S<value>. LED pin assumed if P is omitted. (Requires DIRECT_PIN_CONTROL)
+ * M43 - Display pin status, watch pins for changes, watch endstops & toggle LED, Z servo probe test, toggle pins
+ * M48 - Measure Z Probe repeatability: M48 P<points> X<pos> Y<pos> V<level> E<engage> L<legs> S<chizoid>. (Requires Z_MIN_PROBE_REPEATABILITY_TEST)
+ * M73 - Set the progress percentage. (Requires LCD_SET_PROGRESS_MANUALLY)
+ * M75 - Start the print job timer.
+ * M76 - Pause the print job timer.
+ * M77 - Stop the print job timer.
+ * M78 - Show statistical information about the print jobs. (Requires PRINTCOUNTER)
+ * M80 - Turn on Power Supply. (Requires PSU_CONTROL)
+ * M81 - Turn off Power Supply. (Requires PSU_CONTROL)
+ * M82 - Set E codes absolute (default).
+ * M83 - Set E codes relative while in Absolute (G90) mode.
+ * M84 - Disable steppers until next move, or use S<seconds> to specify an idle
+ * duration after which steppers should turn off. S0 disables the timeout.
+ * M85 - Set inactivity shutdown timer with parameter S<seconds>. To disable set zero (default)
+ * M92 - Set planner.settings.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 - S<temp> Wait for extruder current temp to reach target temp. ** Wait only when heating! **
+ * R<temp> Wait for extruder current temp to reach target temp. ** Wait for heating or cooling. **
+ * If AUTOTEMP is enabled, S<mintemp> B<maxtemp> F<factor>. Exit autotemp by any M109 without F
+ * M110 - Set the current line number. (Used by host printing)
+ * M111 - Set debug flags: "M111 S<flagbits>". See flag bits defined in enum.h.
+ * M112 - Full Shutdown.
+ * M113 - Get or set the timeout interval for Host Keepalive "busy" messages. (Requires HOST_KEEPALIVE_FEATURE)
+ * M114 - Report current position.
+ * M115 - Report capabilities. (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 at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660 or L6470)
+ * 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<temp>
+ * M141 - Set heated chamber target temp. S<temp> (Requires a chamber heater)
+ * M145 - Set heatup values for materials on the LCD. H<hotend> B<bed> F<fan speed> for S<material> (0=PLA, 1=ABS)
+ * M149 - Set temperature units. (Requires TEMPERATURE_UNITS_SUPPORT)
+ * M150 - Set Status LED Color as R<red> U<green> B<blue> W<white> P<bright>. Values 0-255. (Requires BLINKM, RGB_LED, RGBW_LED, NEOPIXEL_LED, PCA9533, or PCA9632).
+ * M155 - Auto-report temperatures with interval of S<seconds>. (Requires AUTO_REPORT_TEMPERATURES)
+ * M163 - Set a single proportion for a mixing extruder. (Requires MIXING_EXTRUDER)
+ * M164 - Commit the mix and save to a virtual tool (current, or as specified by 'S'). (Requires MIXING_EXTRUDER)
+ * M165 - Set the mix for the mixing extruder (and current virtual tool) with parameters ABCDHI. (Requires MIXING_EXTRUDER and DIRECT_MIXING_IN_G1)
+ * M166 - Set the Gradient Mix for the mixing extruder. (Requires GRADIENT_MIX)
+ * M190 - S<temp> Wait for bed current temp to reach target temp. ** Wait only when heating! **
+ * R<temp> Wait for bed current temp to reach target temp. ** Wait for heating or cooling. **
+ * M200 - Set filament diameter, D<diameter>, 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<accel> Y<accel> Z<accel> E<accel>"
+ * M202 - Set max acceleration in units/s^2 for travel moves: "M202 X<accel> Y<accel> Z<accel> E<accel>" ** UNUSED IN MARLIN! **
+ * M203 - Set maximum feedrate: "M203 X<fr> Y<fr> Z<fr> E<fr>" in units/sec.
+ * M204 - Set default acceleration in units/sec^2: P<printing> R<extruder_only> T<travel>
+ * M205 - Set advanced settings. Current units apply:
+ S<print> T<travel> minimum speeds
+ B<minimum segment time>
+ X<max X jerk>, Y<max Y jerk>, Z<max Z jerk>, E<max E jerk>
+ * M206 - Set additional homing offset. (Disabled by NO_WORKSPACE_OFFSETS or DELTA)
+ * M207 - Set Retract Length: S<length>, Feedrate: F<units/min>, and Z lift: Z<distance>. (Requires FWRETRACT)
+ * M208 - Set Recover (unretract) Additional (!) Length: S<length> and Feedrate: F<units/min>. (Requires FWRETRACT)
+ * M209 - Turn Automatic Retract Detection on/off: S<0|1> (For slicers that don't support G10/11). (Requires FWRETRACT_AUTORETRACT)
+ Every normal extrude-only move will be classified as retract depending on the direction.
+ * M211 - Enable, Disable, and/or Report software endstops: S<0|1> (Requires MIN_SOFTWARE_ENDSTOPS or MAX_SOFTWARE_ENDSTOPS)
+ * M217 - Set filament swap parameters: "M217 S<length> P<feedrate> R<feedrate>". (Requires SINGLENOZZLE)
+ * M218 - Set/get a tool offset: "M218 T<index> X<offset> Y<offset>". (Requires 2 or more extruders)
+ * M220 - Set Feedrate Percentage: "M220 S<percent>" (i.e., "FR" on the LCD)
+ * Use "M220 B" to back up the Feedrate Percentage and "M220 R" to restore it. (Requires an MMU_MODEL version 2 or 2S)
+ * M221 - Set Flow Percentage: "M221 S<percent>"
+ * M226 - Wait until a pin is in a given state: "M226 P<pin> S<state>" (Requires DIRECT_PIN_CONTROL)
+ * M240 - Trigger a camera to take a photograph. (Requires PHOTO_GCODE)
+ * M250 - Set LCD contrast: "M250 C<contrast>" (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<index> S<angle|µs>". (Requires servos)
+ * M281 - Set servo min|max position: "M281 P<index> L<min> U<max>". (Requires EDITABLE_SERVO_ANGLES)
+ * M290 - Babystepping (Requires BABYSTEPPING)
+ * M300 - Play beep sound S<frequency Hz> P<duration ms>
+ * M301 - Set PID parameters P I and D. (Requires PIDTEMP)
+ * M302 - Allow cold extrudes, or set the minimum extrude S<temperature>. (Requires PREVENT_COLD_EXTRUSION)
+ * M303 - PID relay autotune S<temperature> sets the target temperature. Default 150C. (Requires PIDTEMP)
+ * M304 - Set bed PID parameters P I and D. (Requires PIDTEMPBED)
+ * M305 - Set user thermistor parameters R T and P. (Requires TEMP_SENSOR_x 1000)
+ * 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)
+ * M403 - Set filament type for PRUSA MMU2
+ * M404 - Display or set the Nominal Filament Width: "W<diameter>". (Requires FILAMENT_WIDTH_SENSOR)
+ * M405 - Enable Filament Sensor flow control. "M405 D<delay_cm>". (Requires FILAMENT_WIDTH_SENSOR)
+ * M406 - Disable Filament Sensor flow control. (Requires FILAMENT_WIDTH_SENSOR)
+ * M407 - Display measured filament diameter in millimeters. (Requires FILAMENT_WIDTH_SENSOR)
+ * M410 - Quickstop. Abort all planned moves.
+ * M412 - Enable / Disable Filament Runout Detection. (Requires FILAMENT_RUNOUT_SENSOR)
+ * M413 - Enable / Disable Power-Loss Recovery. (Requires POWER_LOSS_RECOVERY)
+ * M414 - Set language by index. (Requires LCD_LANGUAGE_2...)
+ * M420 - Enable/Disable Leveling (with current values) S1=enable S0=disable (Requires MESH_BED_LEVELING or ABL)
+ * M421 - Set a single Z coordinate in the Mesh Leveling grid. X<units> Y<units> Z<units> (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_UBL)
+ * M422 - Set Z Stepper automatic alignment position using probe. X<units> Y<units> A<axis> (Requires Z_STEPPER_AUTO_ALIGN)
+ * M425 - Enable/Disable and tune backlash correction. (Requires BACKLASH_COMPENSATION and BACKLASH_GCODE)
+ * M428 - Set the home_offset based on the current_position. Nearest edge applies. (Disabled by NO_WORKSPACE_OFFSETS or DELTA)
+ * M430 - Read the system current, voltage, and power (Requires POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE, or POWER_MONITOR_FIXED_VOLTAGE)
+ * M486 - Identify and cancel objects. (Requires CANCEL_OBJECTS)
+ * 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<verbose>". S0 specifies compact output.
+ * M504 - Validate EEPROM contents. (Requires EEPROM_SETTINGS)
+ * M510 - Lock Printer
+ * M511 - Unlock Printer
+ * M512 - Set/Change/Remove Password
+ * M524 - Abort the current SD print job started with M24. (Requires SDSUPPORT)
+ * M540 - Enable/disable SD card abort on endstop hit: "M540 S<state>". (Requires SD_ABORT_ON_ENDSTOP_HIT)
+ * M552 - Get or set IP address. Enable/disable network interface. (Requires enabled Ethernet port)
+ * M553 - Get or set IP netmask. (Requires enabled Ethernet port)
+ * M554 - Get or set IP gateway. (Requires enabled Ethernet port)
+ * M569 - Enable stealthChop on an axis. (Requires at least one _DRIVER_TYPE to be TMC2130/2160/2208/2209/5130/5160)
+ * M600 - Pause for filament change: "M600 X<pos> Y<pos> Z<raise> E<first_retract> L<later_retract>". (Requires ADVANCED_PAUSE_FEATURE)
+ * M603 - Configure filament change: "M603 T<tool> U<unload_length> L<load_length>". (Requires ADVANCED_PAUSE_FEATURE)
+ * M605 - Set Dual X-Carriage movement mode: "M605 S<mode> [X<x_offset>] [R<temp_offset>]". (Requires DUAL_X_CARRIAGE)
+ * M665 - Set delta configurations: "M665 H<delta height> L<diagonal rod> R<delta radius> S<segments/s> B<calibration radius> X<Alpha angle trim> Y<Beta angle trim> Z<Gamma angle trim> (Requires DELTA)
+ * M666 - Set/get offsets for delta (Requires DELTA) or dual endstops. (Requires [XYZ]_DUAL_ENDSTOPS)
+ * M672 - Set/Reset Duet Smart Effector's sensitivity. (Requires DUET_SMART_EFFECTOR and SMART_EFFECTOR_MOD_PIN)
+ * M701 - Load filament (Requires FILAMENT_LOAD_UNLOAD_GCODES)
+ * M702 - Unload filament (Requires FILAMENT_LOAD_UNLOAD_GCODES)
+ * M808 - Set or Goto a Repeat Marker (Requires GCODE_REPEAT_MARKERS)
+ * M810-M819 - Define/execute a G-code macro (Requires GCODE_MACROS)
+ * M851 - Set Z probe's XYZ offsets in current units. (Negative values: X=left, Y=front, Z=below)
+ * M852 - Set skew factors: "M852 [I<xy>] [J<xz>] [K<yz>]". (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.
+ * M871 - Print/reset/clear first layer temperature offset values. (Requires PROBE_TEMP_COMPENSATION)
+ * M192 - Wait for probe temp (Requires PROBE_TEMP_COMPENSATION)
+ * M876 - Handle Prompt Response. (Requires HOST_PROMPT_SUPPORT and not EMERGENCY_PARSER)
+ * M900 - Get or Set Linear Advance K-factor. (Requires LIN_ADVANCE)
+ * M906 - Set or get motor current in milliamps using axis codes X, Y, Z, E. Report values if no axis codes given. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660 or L6470)
+ * M907 - Set digital trimpot motor current using axis codes. (Requires a board with digital trimpots)
+ * M908 - Control digital trimpot directly. (Requires HAS_MOTOR_CURRENT_DAC or DIGIPOTSS_PIN)
+ * M909 - Print digipot/DAC current value. (Requires HAS_MOTOR_CURRENT_DAC)
+ * M910 - Commit digipot/DAC value to external EEPROM via I2C. (Requires HAS_MOTOR_CURRENT_DAC)
+ * M911 - Report stepper driver overtemperature pre-warn condition. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660)
+ * M912 - Clear stepper driver overtemperature pre-warn condition flag. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660)
+ * M913 - Set HYBRID_THRESHOLD speed. (Requires HYBRID_THRESHOLD)
+ * M914 - Set StallGuard sensitivity. (Requires SENSORLESS_HOMING or SENSORLESS_PROBING)
+ * M916 - L6470 tuning: Increase KVAL_HOLD until thermal warning. (Requires at least one _DRIVER_TYPE L6470)
+ * M917 - L6470 tuning: Find minimum current thresholds. (Requires at least one _DRIVER_TYPE L6470)
+ * M918 - L6470 tuning: Increase speed until max or error. (Requires at least one _DRIVER_TYPE L6470)
+ * M951 - Set Magnetic Parking Extruder parameters. (Requires MAGNETIC_PARKING_EXTRUDER)
+ * M7219 - Control Max7219 Matrix LEDs. (Requires MAX7219_GCODE)
+ *
+ * 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
+ * G425 - Calibrate using a conductive object. (Requires CALIBRATION_GCODE)
+ * M928 - Start SD logging: "M928 filename.gco". Stop with M29. (Requires SDSUPPORT)
+ * M993 - Backup SPI Flash to SD
+ * M994 - Load a Backup from SD to SPI Flash
+ * M995 - Touch screen calibration for TFT display
+ * M997 - Perform in-application firmware update
+ * M999 - Restart after being stopped by error
+ * D... - Custom Development G-code. Add hooks to 'gcode_D.cpp' for developers to test features. (Requires MARLIN_DEV_MODE)
+ *
+ * "T" Codes
+ *
+ * T0-T3 - Select an extruder (tool) by index: "T<n> F<units/min>"
+ */
+
+#include "../inc/MarlinConfig.h"
+#include "parser.h"
+
+#if ENABLED(I2C_POSITION_ENCODERS)
+ #include "../feature/encoder_i2c.h"
+#endif
+
+#if IS_SCARA || defined(G0_FEEDRATE)
+ #define HAS_FAST_MOVES 1
+#endif
+
+enum AxisRelative : uint8_t { REL_X, REL_Y, REL_Z, REL_E, E_MODE_ABS, E_MODE_REL };
+
+extern const char G28_STR[];
+
+class GcodeSuite {
+public:
+
+ static uint8_t axis_relative;
+
+ static inline bool axis_is_relative(const AxisEnum a) {
+ if (a == E_AXIS) {
+ if (TEST(axis_relative, E_MODE_REL)) return true;
+ if (TEST(axis_relative, E_MODE_ABS)) return false;
+ }
+ return TEST(axis_relative, a);
+ }
+ static inline void set_relative_mode(const bool rel) {
+ axis_relative = rel ? _BV(REL_X) | _BV(REL_Y) | _BV(REL_Z) | _BV(REL_E) : 0;
+ }
+ static inline void set_e_relative() {
+ CBI(axis_relative, E_MODE_ABS);
+ SBI(axis_relative, E_MODE_REL);
+ }
+ static inline void set_e_absolute() {
+ CBI(axis_relative, E_MODE_REL);
+ SBI(axis_relative, E_MODE_ABS);
+ }
+
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ /**
+ * Workspace planes only apply to G2/G3 moves
+ * (and "canned cycles" - not a current feature)
+ */
+ enum WorkspacePlane : char { PLANE_XY, PLANE_ZX, PLANE_YZ };
+ static WorkspacePlane workspace_plane;
+ #endif
+
+ #define MAX_COORDINATE_SYSTEMS 9
+ #if ENABLED(CNC_COORDINATE_SYSTEMS)
+ static int8_t active_coordinate_system;
+ static xyz_pos_t coordinate_system[MAX_COORDINATE_SYSTEMS];
+ static bool select_coordinate_system(const int8_t _new);
+ #endif
+
+ static millis_t previous_move_ms, max_inactive_time, stepper_inactive_time;
+ FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; }
+ FORCE_INLINE static bool stepper_max_timed_out(const millis_t ms=millis()) {
+ return max_inactive_time && ELAPSED(ms, previous_move_ms + max_inactive_time);
+ }
+ FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) {
+ return ELAPSED(ms, previous_move_ms + stepper_inactive_time);
+ }
+
+ static int8_t get_target_extruder_from_command();
+ static int8_t get_target_e_stepper_from_command();
+ static void get_destination_from_command();
+
+ static void process_parsed_command(const bool no_ok=false);
+ static void process_next_command();
+
+ // Execute G-code in-place, preserving current G-code parameters
+ static void process_subcommands_now_P(PGM_P pgcode);
+ static void process_subcommands_now(char * gcode);
+
+ static inline void home_all_axes(const bool keep_leveling=false) {
+ process_subcommands_now_P(keep_leveling ? G28_STR : TERN(G28_L0_ENSURES_LEVELING_OFF, PSTR("G28L0"), G28_STR));
+ }
+
+ #if EITHER(HAS_AUTO_REPORTING, HOST_KEEPALIVE_FEATURE)
+ static bool autoreport_paused;
+ static inline bool set_autoreport_paused(const bool p) {
+ const bool was = autoreport_paused;
+ autoreport_paused = p;
+ return was;
+ }
+ #else
+ static constexpr bool autoreport_paused = false;
+ static inline bool set_autoreport_paused(const bool) { return false; }
+ #endif
+
+ #if ENABLED(HOST_KEEPALIVE_FEATURE)
+ /**
+ * States for managing Marlin and host communication
+ * Marlin sends messages if blocked or busy
+ */
+ enum MarlinBusyState : char {
+ NOT_BUSY, // Not in a handler
+ IN_HANDLER, // Processing a GCode
+ IN_PROCESS, // Known to be blocking command input (as in G29)
+ PAUSED_FOR_USER, // Blocking pending any input
+ PAUSED_FOR_INPUT // Blocking pending text input (concept)
+ };
+
+ static MarlinBusyState busy_state;
+ static uint8_t host_keepalive_interval;
+
+ static void host_keepalive();
+
+ #define KEEPALIVE_STATE(N) REMEMBER(_KA_, gcode.busy_state, gcode.N)
+ #else
+ #define KEEPALIVE_STATE(N) NOOP
+ #endif
+
+ static void dwell(millis_t time);
+
+private:
+
+ TERN_(MARLIN_DEV_MODE, static void D(const int16_t dcode));
+
+ static void G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move=false));
+
+ TERN_(ARC_SUPPORT, static void G2_G3(const bool clockwise));
+
+ static void G4();
+
+ TERN_(BEZIER_CURVE_SUPPORT, static void G5());
+
+ TERN_(DIRECT_STEPPING, static void G6());
+
+ #if ENABLED(FWRETRACT)
+ static void G10();
+ static void G11();
+ #endif
+
+ TERN_(NOZZLE_CLEAN_FEATURE, static void G12());
+
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ static void G17();
+ static void G18();
+ static void G19();
+ #endif
+
+ #if ENABLED(INCH_MODE_SUPPORT)
+ static void G20();
+ static void G21();
+ #endif
+
+ TERN_(G26_MESH_VALIDATION, static void G26());
+
+ TERN_(NOZZLE_PARK_FEATURE, static void G27());
+
+ static void G28();
+
+ #if HAS_LEVELING
+ #if ENABLED(G29_RETRY_AND_RECOVER)
+ static void event_probe_failure();
+ static void event_probe_recover();
+ static void G29_with_retry();
+ #define G29_TYPE bool
+ #else
+ #define G29_TYPE void
+ #endif
+ static G29_TYPE G29();
+ #endif
+
+ #if HAS_BED_PROBE
+ static void G30();
+ #if ENABLED(Z_PROBE_SLED)
+ static void G31();
+ static void G32();
+ #endif
+ #endif
+
+ TERN_(DELTA_AUTO_CALIBRATION, static void G33());
+
+ #if ANY(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN, MECHANICAL_GANTRY_CALIBRATION)
+ static void G34();
+ #endif
+
+ TERN_(Z_STEPPER_AUTO_ALIGN, static void M422());
+
+ TERN_(ASSISTED_TRAMMING, static void G35());
+
+ TERN_(G38_PROBE_TARGET, static void G38(const int8_t subcode));
+
+ TERN_(HAS_MESH, static void G42());
+
+ #if ENABLED(CNC_COORDINATE_SYSTEMS)
+ static void G53();
+ static void G54();
+ static void G55();
+ static void G56();
+ static void G57();
+ static void G58();
+ static void G59();
+ #endif
+
+ TERN_(PROBE_TEMP_COMPENSATION, static void G76());
+
+ #if SAVED_POSITIONS
+ static void G60();
+ static void G61();
+ #endif
+
+ TERN_(GCODE_MOTION_MODES, static void G80());
+
+ static void G92();
+
+ TERN_(CALIBRATION_GCODE, static void G425());
+
+ TERN_(HAS_RESUME_CONTINUE, static void M0_M1());
+
+ #if HAS_CUTTER
+ static void M3_M4(const bool is_M4);
+ static void M5();
+ #endif
+
+ #if ENABLED(COOLANT_CONTROL)
+ TERN_(COOLANT_MIST, static void M7());
+ TERN_(COOLANT_FLOOD, static void M8());
+ static void M9();
+ #endif
+
+ TERN_(EXTERNAL_CLOSED_LOOP_CONTROLLER, static void M12());
+
+ TERN_(EXPECTED_PRINTER_CHECK, static void M16());
+
+ static void M17();
+
+ static void M18_M84();
+
+ #if ENABLED(SDSUPPORT)
+ static void M20();
+ static void M21();
+ static void M22();
+ static void M23();
+ static void M24();
+ static void M25();
+ static void M26();
+ static void M27();
+ static void M28();
+ static void M29();
+ static void M30();
+ #endif
+
+ static void M31();
+
+ #if ENABLED(SDSUPPORT)
+ TERN_(HAS_MEDIA_SUBCALLS, static void M32());
+ TERN_(LONG_FILENAME_HOST_SUPPORT, static void M33());
+ #if BOTH(SDCARD_SORT_ALPHA, SDSORT_GCODE)
+ static void M34();
+ #endif
+ #endif
+
+ TERN_(DIRECT_PIN_CONTROL, static void M42());
+ TERN_(PINS_DEBUGGING, static void M43());
+
+ TERN_(Z_MIN_PROBE_REPEATABILITY_TEST, static void M48());
+
+ TERN_(LCD_SET_PROGRESS_MANUALLY, static void M73());
+
+ static void M75();
+ static void M76();
+ static void M77();
+
+ TERN_(PRINTCOUNTER, static void M78());
+
+ TERN_(PSU_CONTROL, static void M80());
+
+ static void M81();
+ static void M82();
+ static void M83();
+ static void M85();
+ static void M92();
+
+ TERN_(M100_FREE_MEMORY_WATCHER, static void M100());
+
+ #if EXTRUDERS
+ static void M104();
+ static void M109();
+ #endif
+
+ static void M105();
+
+ #if HAS_FAN
+ static void M106();
+ static void M107();
+ #endif
+
+ #if DISABLED(EMERGENCY_PARSER)
+ static void M108();
+ static void M112();
+ static void M410();
+ TERN_(HOST_PROMPT_SUPPORT, static void M876());
+ #endif
+
+ static void M110();
+ static void M111();
+
+ TERN_(HOST_KEEPALIVE_FEATURE, static void M113());
+
+ static void M114();
+ static void M115();
+ static void M117();
+ static void M118();
+ static void M119();
+ static void M120();
+ static void M121();
+
+ TERN_(PARK_HEAD_ON_PAUSE, static void M125());
+
+ #if ENABLED(BARICUDA)
+ #if HAS_HEATER_1
+ static void M126();
+ static void M127();
+ #endif
+ #if HAS_HEATER_2
+ static void M128();
+ static void M129();
+ #endif
+ #endif
+
+ #if HAS_HEATED_BED
+ static void M140();
+ static void M190();
+ #endif
+
+ #if HAS_HEATED_CHAMBER
+ static void M141();
+ static void M191();
+ #endif
+
+ #if PREHEAT_COUNT
+ static void M145();
+ #endif
+
+ TERN_(TEMPERATURE_UNITS_SUPPORT, static void M149());
+
+ TERN_(HAS_COLOR_LEDS, static void M150());
+
+ #if BOTH(AUTO_REPORT_TEMPERATURES, HAS_TEMP_SENSOR)
+ static void M155();
+ #endif
+
+ #if ENABLED(MIXING_EXTRUDER)
+ static void M163();
+ static void M164();
+ TERN_(DIRECT_MIXING_IN_G1, static void M165());
+ TERN_(GRADIENT_MIX, static void M166());
+ #endif
+
+ static void M200();
+ static void M201();
+
+ #if 0
+ static void M202(); // Not used for Sprinter/grbl gen6
+ #endif
+
+ static void M203();
+ static void M204();
+ static void M205();
+
+ TERN_(HAS_M206_COMMAND, static void M206());
+
+ #if ENABLED(FWRETRACT)
+ static void M207();
+ static void M208();
+ TERN_(FWRETRACT_AUTORETRACT, static void M209());
+ #endif
+
+ static void M211();
+
+ TERN_(HAS_MULTI_EXTRUDER, static void M217());
+
+ TERN_(HAS_HOTEND_OFFSET, static void M218());
+
+ static void M220();
+
+ #if EXTRUDERS
+ static void M221();
+ #endif
+
+ TERN_(DIRECT_PIN_CONTROL, static void M226());
+
+ TERN_(PHOTO_GCODE, static void M240());
+
+ TERN_(HAS_LCD_CONTRAST, static void M250());
+
+ #if ENABLED(EXPERIMENTAL_I2CBUS)
+ static void M260();
+ static void M261();
+ #endif
+
+ #if HAS_SERVOS
+ static void M280();
+ TERN_(EDITABLE_SERVO_ANGLES, static void M281());
+ #endif
+
+ TERN_(BABYSTEPPING, static void M290());
+
+ TERN_(HAS_BUZZER, static void M300());
+
+ TERN_(PIDTEMP, static void M301());
+
+ TERN_(PREVENT_COLD_EXTRUSION, static void M302());
+
+ TERN_(HAS_PID_HEATING, static void M303());
+
+ TERN_(PIDTEMPBED, static void M304());
+
+ TERN_(HAS_USER_THERMISTORS, static void M305());
+
+ #if HAS_MICROSTEPS
+ static void M350();
+ static void M351();
+ #endif
+
+ TERN_(CASE_LIGHT_ENABLE, static void M355());
+
+ TERN_(REPETIER_GCODE_M360, static void M360());
+
+ #if ENABLED(MORGAN_SCARA)
+ static bool M360();
+ static bool M361();
+ static bool M362();
+ static bool M363();
+ static bool M364();
+ #endif
+
+ #if EITHER(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL)
+ static void M380();
+ static void M381();
+ #endif
+
+ static void M400();
+
+ #if HAS_BED_PROBE
+ static void M401();
+ static void M402();
+ #endif
+
+ TERN_(HAS_PRUSA_MMU2, static void M403());
+
+ #if ENABLED(FILAMENT_WIDTH_SENSOR)
+ static void M404();
+ static void M405();
+ static void M406();
+ static void M407();
+ #endif
+
+ TERN_(HAS_FILAMENT_SENSOR, static void M412());
+
+ TERN_(HAS_MULTI_LANGUAGE, static void M414());
+
+ #if HAS_LEVELING
+ static void M420();
+ static void M421();
+ #endif
+
+ TERN_(BACKLASH_GCODE, static void M425());
+
+ TERN_(HAS_M206_COMMAND, static void M428());
+
+ TERN_(HAS_POWER_MONITOR, static void M430());
+
+ TERN_(CANCEL_OBJECTS, static void M486());
+
+ static void M500();
+ static void M501();
+ static void M502();
+ #if DISABLED(DISABLE_M503)
+ static void M503();
+ #endif
+ TERN_(EEPROM_SETTINGS, static void M504());
+
+ #if ENABLED(PASSWORD_FEATURE)
+ static void M510();
+ TERN_(PASSWORD_UNLOCK_GCODE, static void M511());
+ TERN_(PASSWORD_CHANGE_GCODE, static void M512());
+ #endif
+
+ TERN_(SDSUPPORT, static void M524());
+
+ TERN_(SD_ABORT_ON_ENDSTOP_HIT, static void M540());
+
+ #if HAS_ETHERNET
+ static void M552();
+ static void M553();
+ static void M554();
+ #endif
+
+ TERN_(BAUD_RATE_GCODE, static void M575());
+
+ #if ENABLED(ADVANCED_PAUSE_FEATURE)
+ static void M600();
+ static void M603();
+ #endif
+
+ TERN_(HAS_DUPLICATION_MODE, static void M605());
+
+ TERN_(IS_KINEMATIC, static void M665());
+
+ #if ENABLED(DELTA) || HAS_EXTRA_ENDSTOPS
+ static void M666();
+ #endif
+
+ #if ENABLED(DUET_SMART_EFFECTOR) && PIN_EXISTS(SMART_EFFECTOR_MOD)
+ static void M672();
+ #endif
+
+ #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES)
+ static void M701();
+ static void M702();
+ #endif
+
+ TERN_(GCODE_REPEAT_MARKERS, static void M808());
+
+ TERN_(GCODE_MACROS, static void M810_819());
+
+ TERN_(HAS_BED_PROBE, static void M851());
+
+ TERN_(SKEW_CORRECTION_GCODE, static void M852());
+
+ #if ENABLED(I2C_POSITION_ENCODERS)
+ FORCE_INLINE static void M860() { I2CPEM.M860(); }
+ FORCE_INLINE static void M861() { I2CPEM.M861(); }
+ FORCE_INLINE static void M862() { I2CPEM.M862(); }
+ FORCE_INLINE static void M863() { I2CPEM.M863(); }
+ FORCE_INLINE static void M864() { I2CPEM.M864(); }
+ FORCE_INLINE static void M865() { I2CPEM.M865(); }
+ FORCE_INLINE static void M866() { I2CPEM.M866(); }
+ FORCE_INLINE static void M867() { I2CPEM.M867(); }
+ FORCE_INLINE static void M868() { I2CPEM.M868(); }
+ FORCE_INLINE static void M869() { I2CPEM.M869(); }
+ #endif
+
+ #if ENABLED(PROBE_TEMP_COMPENSATION)
+ static void M192();
+ static void M871();
+ #endif
+
+ TERN_(LIN_ADVANCE, static void M900());
+
+ #if HAS_TRINAMIC_CONFIG
+ static void M122();
+ static void M906();
+ TERN_(HAS_STEALTHCHOP, static void M569());
+ #if ENABLED(MONITOR_DRIVER_STATUS)
+ static void M911();
+ static void M912();
+ #endif
+ TERN_(HYBRID_THRESHOLD, static void M913());
+ TERN_(USE_SENSORLESS, static void M914());
+ #endif
+
+ #if HAS_L64XX
+ static void M122();
+ static void M906();
+ static void M916();
+ static void M917();
+ static void M918();
+ #endif
+
+ #if ANY(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_PWM, HAS_MOTOR_CURRENT_I2C, HAS_MOTOR_CURRENT_DAC)
+ static void M907();
+ #if EITHER(HAS_MOTOR_CURRENT_SPI, HAS_MOTOR_CURRENT_DAC)
+ static void M908();
+ #if ENABLED(HAS_MOTOR_CURRENT_DAC)
+ static void M909();
+ static void M910();
+ #endif
+ #endif
+ #endif
+
+ TERN_(SDSUPPORT, static void M928());
+
+ TERN_(MAGNETIC_PARKING_EXTRUDER, static void M951());
+
+ TERN_(TOUCH_SCREEN_CALIBRATION, static void M995());
+
+ #if BOTH(HAS_SPI_FLASH, SDSUPPORT)
+ static void M993();
+ static void M994();
+ #endif
+
+ TERN_(PLATFORM_M997_SUPPORT, static void M997());
+
+ static void M999();
+
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ static void M413();
+ static void M1000();
+ #endif
+
+ TERN_(SDSUPPORT, static void M1001());
+
+ TERN_(MAX7219_GCODE, static void M7219());
+
+ TERN_(CONTROLLER_FAN_EDITABLE, static void M710());
+
+ static void T(const int8_t tool_index);
+
+};
+
+extern GcodeSuite gcode;
diff --git a/Marlin/src/gcode/gcode_d.cpp b/Marlin/src/gcode/gcode_d.cpp
new file mode 100644
index 0000000..0bd2955
--- /dev/null
+++ b/Marlin/src/gcode/gcode_d.cpp
@@ -0,0 +1,181 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../inc/MarlinConfigPre.h"
+
+#if ENABLED(MARLIN_DEV_MODE)
+
+ #include "gcode.h"
+ #include "../module/settings.h"
+ #include "../module/temperature.h"
+ #include "../libs/hex_print.h"
+ #include "../HAL/shared/eeprom_if.h"
+ #include "../HAL/shared/Delay.h"
+
+ /**
+ * Dn: G-code for development and testing
+ *
+ * See https://reprap.org/wiki/G-code#D:_Debug_codes
+ *
+ * Put whatever else you need here to test ongoing development.
+ */
+ void GcodeSuite::D(const int16_t dcode) {
+ switch (dcode) {
+
+ case -1:
+ for (;;); // forever
+
+ case 0:
+ HAL_reboot();
+ break;
+
+ case 1: {
+ // Zero or pattern-fill the EEPROM data
+ #if ENABLED(EEPROM_SETTINGS)
+ persistentStore.access_start();
+ size_t total = persistentStore.capacity();
+ int pos = 0;
+ const uint8_t value = 0x0;
+ while (total--) persistentStore.write_data(pos, &value, 1);
+ persistentStore.access_finish();
+ #else
+ settings.reset();
+ settings.save();
+ #endif
+ HAL_reboot();
+ } break;
+
+ case 2: { // D2 Read / Write SRAM
+ #define SRAM_SIZE 8192
+ uint8_t *pointer = parser.hex_adr_val('A');
+ uint16_t len = parser.ushortval('C', 1);
+ uintptr_t addr = (uintptr_t)pointer;
+ NOMORE(addr, size_t(SRAM_SIZE - 1));
+ NOMORE(len, SRAM_SIZE - addr);
+ if (parser.seenval('X')) {
+ // Write the hex bytes after the X
+ uint16_t val = parser.hex_val('X');
+ while (len--) {
+ *pointer = val;
+ pointer++;
+ }
+ }
+ else {
+ while (len--) print_hex_byte(*(pointer++));
+ SERIAL_EOL();
+ }
+ } break;
+
+ #if ENABLED(EEPROM_SETTINGS)
+ case 3: { // D3 Read / Write EEPROM
+ uint8_t *pointer = parser.hex_adr_val('A');
+ uint16_t len = parser.ushortval('C', 1);
+ uintptr_t addr = (uintptr_t)pointer;
+ NOMORE(addr, size_t(persistentStore.capacity() - 1));
+ NOMORE(len, persistentStore.capacity() - addr);
+ if (parser.seenval('X')) {
+ uint16_t val = parser.hex_val('X');
+ #if ENABLED(EEPROM_SETTINGS)
+ persistentStore.access_start();
+ while (len--) {
+ int pos = 0;
+ persistentStore.write_data(pos, (uint8_t *)&val, sizeof(val));
+ }
+ SERIAL_EOL();
+ persistentStore.access_finish();
+ #else
+ SERIAL_ECHOLNPGM("NO EEPROM");
+ #endif
+ }
+ else {
+ // Read bytes from EEPROM
+ #if ENABLED(EEPROM_SETTINGS)
+ persistentStore.access_start();
+ int pos = 0;
+ uint8_t val;
+ while (len--) if (!persistentStore.read_data(pos, &val, 1)) print_hex_byte(val);
+ SERIAL_EOL();
+ persistentStore.access_finish();
+ #else
+ SERIAL_ECHOLNPGM("NO EEPROM");
+ len = 0;
+ #endif
+ SERIAL_EOL();
+ }
+ } break;
+ #endif
+
+ case 4: { // D4 Read / Write PIN
+ // const uint8_t pin = parser.byteval('P');
+ // const bool is_out = parser.boolval('F'),
+ // val = parser.byteval('V', LOW);
+ if (parser.seenval('X')) {
+ // TODO: Write the hex bytes after the X
+ //while (len--) {
+ //}
+ }
+ else {
+ // while (len--) {
+ // TODO: Read bytes from EEPROM
+ // print_hex_byte(eeprom_read_byte(*(adr++));
+ // }
+ SERIAL_EOL();
+ }
+ } break;
+
+ case 5: { // D4 Read / Write onboard Flash
+ #define FLASH_SIZE 1024
+ uint8_t *pointer = parser.hex_adr_val('A');
+ uint16_t len = parser.ushortval('C', 1);
+ uintptr_t addr = (uintptr_t)pointer;
+ NOMORE(addr, size_t(FLASH_SIZE - 1));
+ NOMORE(len, FLASH_SIZE - addr);
+ if (parser.seenval('X')) {
+ // TODO: Write the hex bytes after the X
+ //while (len--) {
+ //}
+ }
+ else {
+ // while (len--) {
+ // TODO: Read bytes from EEPROM
+ // print_hex_byte(eeprom_read_byte(adr++));
+ // }
+ SERIAL_EOL();
+ }
+ } break;
+
+ case 100: { // D100 Disable heaters and attempt a hard hang (Watchdog Test)
+ SERIAL_ECHOLNPGM("Disabling heaters and attempting to trigger Watchdog");
+ SERIAL_ECHOLNPGM("(USE_WATCHDOG " TERN(USE_WATCHDOG, "ENABLED", "DISABLED") ")");
+ thermalManager.disable_all_heaters();
+ delay(1000); // Allow time to print
+ DISABLE_ISRS();
+ // Use a low-level delay that does not rely on interrupts to function
+ // Do not spin forever, to avoid thermal risks if heaters are enabled and
+ // watchdog does not work.
+ for (int i = 10000; i--;) DELAY_US(1000UL);
+ ENABLE_ISRS();
+ SERIAL_ECHOLNPGM("FAILURE: Watchdog did not trigger board reset.");
+ }
+ }
+ }
+
+#endif
diff --git a/Marlin/src/gcode/geometry/G17-G19.cpp b/Marlin/src/gcode/geometry/G17-G19.cpp
new file mode 100644
index 0000000..7510eab
--- /dev/null
+++ b/Marlin/src/gcode/geometry/G17-G19.cpp
@@ -0,0 +1,53 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(CNC_WORKSPACE_PLANES)
+
+#include "../gcode.h"
+
+inline void report_workspace_plane() {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("Workspace Plane ");
+ serialprintPGM(
+ gcode.workspace_plane == GcodeSuite::PLANE_YZ ? PSTR("YZ\n")
+ : gcode.workspace_plane == GcodeSuite::PLANE_ZX ? PSTR("ZX\n")
+ : PSTR("XY\n")
+ );
+}
+
+inline void set_workspace_plane(const GcodeSuite::WorkspacePlane plane) {
+ gcode.workspace_plane = plane;
+ if (DEBUGGING(INFO)) report_workspace_plane();
+}
+
+/**
+ * G17: Select Plane XY
+ * G18: Select Plane ZX
+ * G19: Select Plane YZ
+ */
+void GcodeSuite::G17() { set_workspace_plane(PLANE_XY); }
+void GcodeSuite::G18() { set_workspace_plane(PLANE_ZX); }
+void GcodeSuite::G19() { set_workspace_plane(PLANE_YZ); }
+
+#endif // CNC_WORKSPACE_PLANES
diff --git a/Marlin/src/gcode/geometry/G53-G59.cpp b/Marlin/src/gcode/geometry/G53-G59.cpp
new file mode 100644
index 0000000..05bc522
--- /dev/null
+++ b/Marlin/src/gcode/geometry/G53-G59.cpp
@@ -0,0 +1,101 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+
+#if ENABLED(CNC_COORDINATE_SYSTEMS)
+
+#include "../../module/stepper.h"
+
+//#define DEBUG_M53
+
+/**
+ * Select a coordinate system and update the workspace offset.
+ * System index -1 is used to specify machine-native.
+ */
+bool GcodeSuite::select_coordinate_system(const int8_t _new) {
+ if (active_coordinate_system == _new) return false;
+ active_coordinate_system = _new;
+ xyz_float_t new_offset{0};
+ if (WITHIN(_new, 0, MAX_COORDINATE_SYSTEMS - 1))
+ new_offset = coordinate_system[_new];
+ LOOP_XYZ(i) {
+ if (position_shift[i] != new_offset[i]) {
+ position_shift[i] = new_offset[i];
+ update_workspace_offset((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.
+ */
+void GcodeSuite::G53() {
+ const int8_t old_system = active_coordinate_system;
+ select_coordinate_system(-1); // Always remove workspace offsets
+ #ifdef DEBUG_M53
+ SERIAL_ECHOLNPGM("Go to native space");
+ report_current_position();
+ #endif
+
+ if (parser.chain()) { // Command to chain?
+ process_parsed_command(); // ...process the chained command
+ select_coordinate_system(old_system);
+ #ifdef DEBUG_M53
+ SERIAL_ECHOLNPAIR("Go back to workspace ", old_system);
+ report_current_position();
+ #endif
+ }
+}
+
+/**
+ * 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.
+ */
+void G54_59(uint8_t subcode=0) {
+ const int8_t _space = parser.codenum - 54 + subcode;
+ if (gcode.select_coordinate_system(_space)) {
+ SERIAL_ECHOLNPAIR("Select workspace ", _space);
+ report_current_position();
+ }
+}
+void GcodeSuite::G54() { G54_59(); }
+void GcodeSuite::G55() { G54_59(); }
+void GcodeSuite::G56() { G54_59(); }
+void GcodeSuite::G57() { G54_59(); }
+void GcodeSuite::G58() { G54_59(); }
+void GcodeSuite::G59() { G54_59(parser.subcode); }
+
+#endif // CNC_COORDINATE_SYSTEMS
diff --git a/Marlin/src/gcode/geometry/G92.cpp b/Marlin/src/gcode/geometry/G92.cpp
new file mode 100644
index 0000000..1a0382e
--- /dev/null
+++ b/Marlin/src/gcode/geometry/G92.cpp
@@ -0,0 +1,105 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/stepper.h"
+
+#if ENABLED(I2C_POSITION_ENCODERS)
+ #include "../../feature/encoder_i2c.h"
+#endif
+
+/**
+ * G92: Set current position to given X Y Z E
+ */
+void GcodeSuite::G92() {
+
+ bool sync_E = false, sync_XYZ = false;
+
+ #if ENABLED(USE_GCODE_SUBCODES)
+ const uint8_t subcode_G92 = parser.subcode;
+ #else
+ constexpr uint8_t subcode_G92 = 0;
+ #endif
+
+ switch (subcode_G92) {
+ default: break;
+ #if ENABLED(CNC_COORDINATE_SYSTEMS)
+ case 1: {
+ // Zero the G92 values and restore current position
+ #if !IS_SCARA
+ LOOP_XYZ(i) if (position_shift[i]) {
+ position_shift[i] = 0;
+ update_workspace_offset((AxisEnum)i);
+ }
+ #endif // Not SCARA
+ } return;
+ #endif
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ case 9: {
+ LOOP_XYZE(i) {
+ if (parser.seenval(axis_codes[i])) {
+ current_position[i] = parser.value_axis_units((AxisEnum)i);
+ if (i == E_AXIS) sync_E = true; else sync_XYZ = true;
+ }
+ }
+ } break;
+ #endif
+ case 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) sync_E = true; else sync_XYZ = true;
+ current_position[i] = v; // Without workspaces revert to Marlin 1.0 behavior
+ #elif HAS_POSITION_SHIFT
+ if (i == E_AXIS) {
+ sync_E = true;
+ current_position.e = v; // When using coordinate spaces, only E is set directly
+ }
+ else {
+ position_shift[i] += d; // Other axes simply offset the coordinate space
+ update_workspace_offset((AxisEnum)i);
+ }
+ #endif
+ }
+ }
+ }
+ } break;
+ }
+
+ #if ENABLED(CNC_COORDINATE_SYSTEMS)
+ // Apply workspace offset to the active coordinate system
+ if (WITHIN(active_coordinate_system, 0, MAX_COORDINATE_SYSTEMS - 1))
+ coordinate_system[active_coordinate_system] = position_shift;
+ #endif
+
+ if (sync_XYZ) sync_plan_position();
+ else if (sync_E) sync_plan_position_e();
+
+ #if DISABLED(DIRECT_STEPPING)
+ report_current_position();
+ #endif
+}
diff --git a/Marlin/src/gcode/geometry/M206_M428.cpp b/Marlin/src/gcode/geometry/M206_M428.cpp
new file mode 100644
index 0000000..2a2cdb1
--- /dev/null
+++ b/Marlin/src/gcode/geometry/M206_M428.cpp
@@ -0,0 +1,94 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_M206_COMMAND
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../lcd/marlinui.h"
+#include "../../libs/buzzer.h"
+#include "../../MarlinCore.h"
+
+void m206_report() {
+ SERIAL_ECHOLNPAIR_P(PSTR("M206 X"), home_offset.x, SP_Y_STR, home_offset.y, SP_Z_STR, home_offset.z);
+}
+
+/**
+ * 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 2.0 release, it will simply be disabled by default.
+ */
+void GcodeSuite::M206() {
+ LOOP_XYZ(i)
+ if (parser.seen(XYZ_CHAR(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
+
+ if (!parser.seen("XYZ"))
+ m206_report();
+ else
+ report_current_position();
+}
+
+/**
+ * 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.
+ */
+void GcodeSuite::M428() {
+ if (homing_needed_error()) return;
+
+ xyz_float_t diff;
+ 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_MSG(STR_ERR_M428_TOO_FAR);
+ LCD_ALERTMESSAGEPGM_P(PSTR("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
diff --git a/Marlin/src/gcode/host/M110.cpp b/Marlin/src/gcode/host/M110.cpp
new file mode 100644
index 0000000..b12b38e
--- /dev/null
+++ b/Marlin/src/gcode/host/M110.cpp
@@ -0,0 +1,34 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../queue.h" // for last_N
+
+/**
+ * M110: Set Current Line Number
+ */
+void GcodeSuite::M110() {
+
+ if (parser.seenval('N'))
+ queue.last_N[queue.command_port()] = parser.value_long();
+
+}
diff --git a/Marlin/src/gcode/host/M113.cpp b/Marlin/src/gcode/host/M113.cpp
new file mode 100644
index 0000000..ce826d6
--- /dev/null
+++ b/Marlin/src/gcode/host/M113.cpp
@@ -0,0 +1,45 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(HOST_KEEPALIVE_FEATURE)
+
+#include "../gcode.h"
+
+/**
+ * M113: Get or set Host Keepalive interval (0 to disable)
+ *
+ * S<seconds> Optional. Set the keepalive interval.
+ */
+void GcodeSuite::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 // HOST_KEEPALIVE_FEATURE
diff --git a/Marlin/src/gcode/host/M114.cpp b/Marlin/src/gcode/host/M114.cpp
new file mode 100644
index 0000000..75356ff
--- /dev/null
+++ b/Marlin/src/gcode/host/M114.cpp
@@ -0,0 +1,214 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/stepper.h"
+
+#if ENABLED(M114_DETAIL)
+
+ #if HAS_L64XX
+ #include "../../libs/L64XX/L64XX_Marlin.h"
+ #define DEBUG_OUT ENABLED(L6470_CHITCHAT)
+ #include "../../core/debug_out.h"
+ #endif
+
+ void report_xyze(const xyze_pos_t &pos, const uint8_t n=XYZE, const uint8_t precision=3) {
+ char str[12];
+ LOOP_L_N(a, n) {
+ SERIAL_CHAR(' ', axis_codes[a], ':');
+ if (pos[a] >= 0) SERIAL_CHAR(' ');
+ SERIAL_ECHO(dtostrf(pos[a], 1, precision, str));
+ }
+ SERIAL_EOL();
+ }
+ inline void report_xyz(const xyze_pos_t &pos) { report_xyze(pos, XYZ); }
+
+ void report_xyz(const xyz_pos_t &pos, const uint8_t precision=3) {
+ char str[12];
+ LOOP_XYZ(a) {
+ SERIAL_CHAR(' ', XYZ_CHAR(a), ':');
+ SERIAL_ECHO(dtostrf(pos[a], 1, precision, str));
+ }
+ SERIAL_EOL();
+ }
+
+ void report_current_position_detail() {
+ // Position as sent by G-code
+ SERIAL_ECHOPGM("\nLogical:");
+ report_xyz(current_position.asLogical());
+
+ // Cartesian position in native machine space
+ SERIAL_ECHOPGM("Raw: ");
+ report_xyz(current_position);
+
+ xyze_pos_t leveled = current_position;
+
+ #if HAS_LEVELING
+ // Current position with leveling applied
+ SERIAL_ECHOPGM("Leveled:");
+ planner.apply_leveling(leveled);
+ report_xyz(leveled);
+
+ // Test planner un-leveling. This should match the Raw result.
+ SERIAL_ECHOPGM("UnLevel:");
+ xyze_pos_t unleveled = leveled;
+ planner.unapply_leveling(unleveled);
+ report_xyz(unleveled);
+ #endif
+
+ #if IS_KINEMATIC
+ // Kinematics applied to the leveled position
+ SERIAL_ECHOPGM(TERN(IS_SCARA, "ScaraK: ", "DeltaK: "));
+ inverse_kinematics(leveled); // writes delta[]
+ report_xyz(delta);
+ #endif
+
+ planner.synchronize();
+
+ #if HAS_L64XX
+ char temp_buf[80];
+ int32_t temp;
+ //#define ABS_POS_SIGN_MASK 0b1111 1111 1110 0000 0000 0000 0000 0000
+ #define ABS_POS_SIGN_MASK 0b11111111111000000000000000000000
+ #define REPORT_ABSOLUTE_POS(Q) do{ \
+ L64xxManager.say_axis(Q, false); \
+ temp = L6470_GETPARAM(L6470_ABS_POS,Q); \
+ if (temp & ABS_POS_SIGN_MASK) temp |= ABS_POS_SIGN_MASK; \
+ sprintf_P(temp_buf, PSTR(":%8ld "), temp); \
+ DEBUG_ECHO(temp_buf); \
+ }while(0)
+
+ DEBUG_ECHOPGM("\nL6470:");
+ #if AXIS_IS_L64XX(X)
+ REPORT_ABSOLUTE_POS(X);
+ #endif
+ #if AXIS_IS_L64XX(X2)
+ REPORT_ABSOLUTE_POS(X2);
+ #endif
+ #if AXIS_IS_L64XX(Y)
+ REPORT_ABSOLUTE_POS(Y);
+ #endif
+ #if AXIS_IS_L64XX(Y2)
+ REPORT_ABSOLUTE_POS(Y2);
+ #endif
+ #if AXIS_IS_L64XX(Z)
+ REPORT_ABSOLUTE_POS(Z);
+ #endif
+ #if AXIS_IS_L64XX(Z2)
+ REPORT_ABSOLUTE_POS(Z2);
+ #endif
+ #if AXIS_IS_L64XX(Z3)
+ REPORT_ABSOLUTE_POS(Z3);
+ #endif
+ #if AXIS_IS_L64XX(Z4)
+ REPORT_ABSOLUTE_POS(Z4);
+ #endif
+ #if AXIS_IS_L64XX(E0)
+ REPORT_ABSOLUTE_POS(E0);
+ #endif
+ #if AXIS_IS_L64XX(E1)
+ REPORT_ABSOLUTE_POS(E1);
+ #endif
+ #if AXIS_IS_L64XX(E2)
+ REPORT_ABSOLUTE_POS(E2);
+ #endif
+ #if AXIS_IS_L64XX(E3)
+ REPORT_ABSOLUTE_POS(E3);
+ #endif
+ #if AXIS_IS_L64XX(E4)
+ REPORT_ABSOLUTE_POS(E4);
+ #endif
+ #if AXIS_IS_L64XX(E5)
+ REPORT_ABSOLUTE_POS(E5);
+ #endif
+ #if AXIS_IS_L64XX(E6)
+ REPORT_ABSOLUTE_POS(E6);
+ #endif
+ #if AXIS_IS_L64XX(E7)
+ REPORT_ABSOLUTE_POS(E7);
+ #endif
+ SERIAL_EOL();
+ #endif // HAS_L64XX
+
+ SERIAL_ECHOPGM("Stepper:");
+ LOOP_XYZE(i) {
+ SERIAL_CHAR(' ', axis_codes[i], ':');
+ SERIAL_ECHO(stepper.position((AxisEnum)i));
+ }
+ SERIAL_EOL();
+
+ #if IS_SCARA
+ const xy_float_t deg = {
+ planner.get_axis_position_degrees(A_AXIS),
+ planner.get_axis_position_degrees(B_AXIS)
+ };
+ SERIAL_ECHOPGM("Degrees:");
+ report_xyze(deg, 2);
+ #endif
+
+ SERIAL_ECHOPGM("FromStp:");
+ get_cartesian_from_steppers(); // writes 'cartes' (with forward kinematics)
+ xyze_pos_t from_steppers = { cartes.x, cartes.y, cartes.z, planner.get_axis_position_mm(E_AXIS) };
+ report_xyze(from_steppers);
+
+ const xyze_float_t diff = from_steppers - leveled;
+ SERIAL_ECHOPGM("Diff: ");
+ report_xyze(diff);
+ }
+
+#endif // M114_DETAIL
+
+/**
+ * M114: Report the current position to host.
+ * Since steppers are moving, the count positions are
+ * projected by using planner calculations.
+ * D - Report more detail. This syncs the planner. (Requires M114_DETAIL)
+ * E - Report E stepper position (Requires M114_DETAIL)
+ * R - Report the realtime position instead of projected.
+ */
+void GcodeSuite::M114() {
+
+ #if ENABLED(M114_DETAIL)
+ if (parser.seen('D')) {
+ #if DISABLED(M114_LEGACY)
+ planner.synchronize();
+ #endif
+ report_current_position();
+ report_current_position_detail();
+ return;
+ }
+ if (parser.seen('E')) {
+ SERIAL_ECHOLNPAIR("Count E:", stepper.position(E_AXIS));
+ return;
+ }
+ #endif
+
+ #if ENABLED(M114_REALTIME)
+ if (parser.seen('R')) { report_real_position(); return; }
+ #endif
+
+ TERN_(M114_LEGACY, planner.synchronize());
+ report_current_position_projected();
+}
diff --git a/Marlin/src/gcode/host/M115.cpp b/Marlin/src/gcode/host/M115.cpp
new file mode 100644
index 0000000..0501f3f
--- /dev/null
+++ b/Marlin/src/gcode/host/M115.cpp
@@ -0,0 +1,171 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(M115_GEOMETRY_REPORT)
+ #include "../../module/motion.h"
+#endif
+
+#if ENABLED(CASE_LIGHT_ENABLE)
+ #include "../../feature/caselight.h"
+#endif
+
+#if ENABLED(EXTENDED_CAPABILITIES_REPORT)
+ static void cap_line(PGM_P const name, bool ena=false) {
+ SERIAL_ECHOPGM("Cap:");
+ serialprintPGM(name);
+ SERIAL_CHAR(':', ena ? '1' : '0');
+ SERIAL_EOL();
+ }
+#endif
+
+/**
+ * M115: Capabilities string and extended capabilities report
+ * If a capability is not reported, hosts should assume
+ * the capability is not present.
+ */
+void GcodeSuite::M115() {
+ SERIAL_ECHOLNPGM(
+ "FIRMWARE_NAME:Marlin " DETAILED_BUILD_VERSION " (" __DATE__ " " __TIME__ ") "
+ "SOURCE_CODE_URL:" SOURCE_CODE_URL " "
+ "PROTOCOL_VERSION:" PROTOCOL_VERSION " "
+ "MACHINE_TYPE:" MACHINE_NAME " "
+ "EXTRUDER_COUNT:" STRINGIFY(EXTRUDERS) " "
+ #ifdef MACHINE_UUID
+ "UUID:" MACHINE_UUID
+ #endif
+ );
+
+ #if ENABLED(EXTENDED_CAPABILITIES_REPORT)
+
+ // PAREN_COMMENTS
+ TERN_(PAREN_COMMENTS, cap_line(PSTR("PAREN_COMMENTS"), true));
+
+ // QUOTED_STRINGS
+ TERN_(GCODE_QUOTED_STRINGS, cap_line(PSTR("QUOTED_STRINGS"), true));
+
+ // SERIAL_XON_XOFF
+ cap_line(PSTR("SERIAL_XON_XOFF"), ENABLED(SERIAL_XON_XOFF));
+
+ // BINARY_FILE_TRANSFER (M28 B1)
+ cap_line(PSTR("BINARY_FILE_TRANSFER"), ENABLED(BINARY_FILE_TRANSFER));
+
+ // EEPROM (M500, M501)
+ cap_line(PSTR("EEPROM"), ENABLED(EEPROM_SETTINGS));
+
+ // Volumetric Extrusion (M200)
+ cap_line(PSTR("VOLUMETRIC"), DISABLED(NO_VOLUMETRICS));
+
+ // AUTOREPORT_TEMP (M155)
+ cap_line(PSTR("AUTOREPORT_TEMP"), ENABLED(AUTO_REPORT_TEMPERATURES));
+
+ // PROGRESS (M530 S L, M531 <file>, 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"), ENABLED(HAS_AUTOLEVEL));
+
+ // RUNOUT (M412, M600)
+ cap_line(PSTR("RUNOUT"), ENABLED(FILAMENT_RUNOUT_SENSOR));
+
+ // Z_PROBE (G30)
+ cap_line(PSTR("Z_PROBE"), ENABLED(HAS_BED_PROBE));
+
+ // MESH_REPORT (M420 V)
+ cap_line(PSTR("LEVELING_DATA"), ENABLED(HAS_LEVELING));
+
+ // BUILD_PERCENT (M73)
+ cap_line(PSTR("BUILD_PERCENT"), ENABLED(LCD_SET_PROGRESS_MANUALLY));
+
+ // SOFTWARE_POWER (M80, M81)
+ cap_line(PSTR("SOFTWARE_POWER"), ENABLED(PSU_CONTROL));
+
+ // TOGGLE_LIGHTS (M355)
+ cap_line(PSTR("TOGGLE_LIGHTS"), ENABLED(CASE_LIGHT_ENABLE));
+ cap_line(PSTR("CASE_LIGHT_BRIGHTNESS"), TERN0(CASE_LIGHT_ENABLE, TERN0(CASELIGHT_USES_BRIGHTNESS, TERN(CASE_LIGHT_USE_NEOPIXEL, true, PWM_PIN(CASE_LIGHT_PIN)))));
+
+ // EMERGENCY_PARSER (M108, M112, M410, M876)
+ cap_line(PSTR("EMERGENCY_PARSER"), ENABLED(EMERGENCY_PARSER));
+
+ // PROMPT SUPPORT (M876)
+ cap_line(PSTR("PROMPT_SUPPORT"), ENABLED(HOST_PROMPT_SUPPORT));
+
+ // SDCARD (M20, M23, M24, etc.)
+ cap_line(PSTR("SDCARD"), ENABLED(SDSUPPORT));
+
+ // REPEAT (M808)
+ cap_line(PSTR("REPEAT"), ENABLED(GCODE_REPEAT_MARKERS));
+
+ // AUTOREPORT_SD_STATUS (M27 extension)
+ cap_line(PSTR("AUTOREPORT_SD_STATUS"), ENABLED(AUTO_REPORT_SD_STATUS));
+
+ // LONG_FILENAME_HOST_SUPPORT (M33)
+ cap_line(PSTR("LONG_FILENAME"), ENABLED(LONG_FILENAME_HOST_SUPPORT));
+
+ // THERMAL_PROTECTION
+ cap_line(PSTR("THERMAL_PROTECTION"), ENABLED(THERMALLY_SAFE));
+
+ // MOTION_MODES (M80-M89)
+ cap_line(PSTR("MOTION_MODES"), ENABLED(GCODE_MOTION_MODES));
+
+ // ARC_SUPPORT (G2-G3)
+ cap_line(PSTR("ARCS"), ENABLED(ARC_SUPPORT));
+
+ // BABYSTEPPING (M290)
+ cap_line(PSTR("BABYSTEPPING"), ENABLED(BABYSTEPPING));
+
+ // CHAMBER_TEMPERATURE (M141, M191)
+ cap_line(PSTR("CHAMBER_TEMPERATURE"), ENABLED(HAS_HEATED_CHAMBER));
+
+ // MEATPACK Compresson
+ cap_line(PSTR("MEATPACK"), ENABLED(MEATPACK));
+
+ // Machine Geometry
+ #if ENABLED(M115_GEOMETRY_REPORT)
+ const xyz_pos_t dmin = { X_MIN_POS, Y_MIN_POS, Z_MIN_POS },
+ dmax = { X_MAX_POS, Y_MAX_POS, Z_MAX_POS };
+ xyz_pos_t cmin = dmin, cmax = dmax;
+ apply_motion_limits(cmin);
+ apply_motion_limits(cmax);
+ const xyz_pos_t lmin = dmin.asLogical(), lmax = dmax.asLogical(),
+ wmin = cmin.asLogical(), wmax = cmax.asLogical();
+ SERIAL_ECHOLNPAIR(
+ "area:{"
+ "full:{"
+ "min:{x:", lmin.x, ",y:", lmin.y, ",z:", lmin.z, "},"
+ "max:{x:", lmax.x, ",y:", lmax.y, ",z:", lmax.z, "}"
+ "},"
+ "work:{"
+ "min:{x:", wmin.x, ",y:", wmin.y, ",z:", wmin.z, "},"
+ "max:{x:", wmax.x, ",y:", wmax.y, ",z:", wmax.z, "}",
+ "}"
+ "}"
+ );
+ #endif
+
+ #endif // EXTENDED_CAPABILITIES_REPORT
+}
diff --git a/Marlin/src/gcode/host/M118.cpp b/Marlin/src/gcode/host/M118.cpp
new file mode 100644
index 0000000..73115d5
--- /dev/null
+++ b/Marlin/src/gcode/host/M118.cpp
@@ -0,0 +1,66 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../core/serial.h"
+
+/**
+ * M118: Display a message in the host console.
+ *
+ * A1 Prepend '// ' for an action command, as in OctoPrint
+ * E1 Have the host 'echo:' the text
+ * Pn Redirect to another serial port
+ * 0 : Announce to all ports
+ * 1-9 : Serial ports 1 to 9
+ */
+void GcodeSuite::M118() {
+ bool hasE = false, hasA = false;
+ #if HAS_MULTI_SERIAL
+ int8_t port = -1; // Assume no redirect
+ #endif
+ char *p = parser.string_arg;
+ for (uint8_t i = 3; i--;) {
+ // A1, E1, and Pn are always parsed out
+ if (!( ((p[0] == 'A' || p[0] == 'E') && p[1] == '1') || (p[0] == 'P' && NUMERIC(p[1])) )) break;
+ switch (p[0]) {
+ case 'A': hasA = true; break;
+ case 'E': hasE = true; break;
+ #if HAS_MULTI_SERIAL
+ case 'P': port = p[1] - '0'; break;
+ #endif
+ }
+ p += 2;
+ while (*p == ' ') ++p;
+ }
+
+ #if HAS_MULTI_SERIAL
+ const int8_t old_serial = multiSerial.portMask;
+ if (WITHIN(port, 0, NUM_SERIAL))
+ multiSerial.portMask = port ? _BV(port - 1) : SERIAL_ALL;
+ #endif
+
+ if (hasE) SERIAL_ECHO_START();
+ if (hasA) SERIAL_ECHOPGM("//");
+ SERIAL_ECHOLN(p);
+
+ TERN_(HAS_MULTI_SERIAL, multiSerial.portMask = old_serial);
+}
diff --git a/Marlin/src/gcode/host/M119.cpp b/Marlin/src/gcode/host/M119.cpp
new file mode 100644
index 0000000..f0066bd
--- /dev/null
+++ b/Marlin/src/gcode/host/M119.cpp
@@ -0,0 +1,33 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/endstops.h"
+
+/**
+ * M119: Output endstop states to serial output
+ */
+void GcodeSuite::M119() {
+
+ endstops.report_states();
+
+}
diff --git a/Marlin/src/gcode/host/M16.cpp b/Marlin/src/gcode/host/M16.cpp
new file mode 100644
index 0000000..1ac8580
--- /dev/null
+++ b/Marlin/src/gcode/host/M16.cpp
@@ -0,0 +1,40 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(EXPECTED_PRINTER_CHECK)
+
+#include "../gcode.h"
+#include "../../MarlinCore.h"
+
+/**
+ * M16: Expected Printer Check
+ */
+void GcodeSuite::M16() {
+
+ if (strcmp_P(parser.string_arg, PSTR(MACHINE_NAME)))
+ kill(GET_TEXT(MSG_KILL_EXPECTED_PRINTER));
+
+}
+
+#endif
diff --git a/Marlin/src/gcode/host/M360.cpp b/Marlin/src/gcode/host/M360.cpp
new file mode 100644
index 0000000..f49a32c
--- /dev/null
+++ b/Marlin/src/gcode/host/M360.cpp
@@ -0,0 +1,186 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(REPETIER_GCODE_M360)
+
+#include "../gcode.h"
+
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+
+#if EXTRUDERS
+ #include "../../module/temperature.h"
+#endif
+
+static void config_prefix(PGM_P const name, PGM_P const pref=nullptr, const int8_t ind=-1) {
+ SERIAL_ECHOPGM("Config:");
+ if (pref) serialprintPGM(pref);
+ if (ind >= 0) { SERIAL_ECHO(int(ind)); SERIAL_CHAR(':'); }
+ serialprintPGM(name);
+ SERIAL_CHAR(':');
+}
+static void config_line(PGM_P const name, const float val, PGM_P const pref=nullptr, const int8_t ind=-1) {
+ config_prefix(name, pref, ind);
+ SERIAL_ECHOLN(val);
+}
+static void config_line_e(const int8_t e, PGM_P const name, const float val) {
+ config_line(name, val, PSTR("Extr."), e + 1);
+}
+
+/**
+ * M360: Report Firmware configuration
+ * in RepRapFirmware-compatible format
+ */
+void GcodeSuite::M360() {
+ PGMSTR(X_STR, "X");
+ PGMSTR(Y_STR, "Y");
+ PGMSTR(Z_STR, "Z");
+ PGMSTR(JERK_STR, "Jerk");
+
+ //
+ // Basics and Enabled items
+ //
+ config_line(PSTR("Baudrate"), BAUDRATE);
+ config_line(PSTR("InputBuffer"), MAX_CMD_SIZE);
+ config_line(PSTR("PrintlineCache"), BUFSIZE);
+ config_line(PSTR("MixingExtruder"), ENABLED(MIXING_EXTRUDER));
+ config_line(PSTR("SDCard"), ENABLED(SDSUPPORT));
+ config_line(PSTR("Fan"), ENABLED(HAS_FAN));
+ config_line(PSTR("LCD"), ENABLED(HAS_DISPLAY));
+ config_line(PSTR("SoftwarePowerSwitch"), 1);
+ config_line(PSTR("SupportLocalFilamentchange"), ENABLED(ADVANCED_PAUSE_FEATURE));
+ config_line(PSTR("CaseLights"), ENABLED(CASE_LIGHT_ENABLE));
+ config_line(PSTR("ZProbe"), ENABLED(HAS_BED_PROBE));
+ config_line(PSTR("Autolevel"), ENABLED(HAS_LEVELING));
+ config_line(PSTR("EEPROM"), ENABLED(EEPROM_SETTINGS));
+
+ //
+ // Homing Directions
+ //
+ PGMSTR(H_DIR_STR, "HomeDir");
+ config_line(H_DIR_STR, X_HOME_DIR, X_STR);
+ config_line(H_DIR_STR, Y_HOME_DIR, Y_STR);
+ config_line(H_DIR_STR, Z_HOME_DIR, Z_STR);
+
+ //
+ // XYZ Axis Jerk
+ //
+ #if HAS_CLASSIC_JERK
+ if (planner.max_jerk.x == planner.max_jerk.y)
+ config_line(PSTR("XY"), planner.max_jerk.x, JERK_STR);
+ else {
+ config_line(X_STR, planner.max_jerk.x, JERK_STR);
+ config_line(Y_STR, planner.max_jerk.y, JERK_STR);
+ }
+ config_line(Z_STR, planner.max_jerk.z, JERK_STR);
+ #endif
+
+ //
+ // Firmware Retraction
+ //
+ config_line(PSTR("SupportG10G11"), ENABLED(FWRETRACT));
+ #if ENABLED(FWRETRACT)
+ PGMSTR(RET_STR, "Retraction");
+ PGMSTR(UNRET_STR, "RetractionUndo");
+ PGMSTR(SPEED_STR, "Speed");
+ // M10 Retract with swap (long) moves
+ config_line(PSTR("Length"), fwretract.settings.retract_length, RET_STR);
+ config_line(SPEED_STR, fwretract.settings.retract_feedrate_mm_s, RET_STR);
+ config_line(PSTR("ZLift"), fwretract.settings.retract_zraise, RET_STR);
+ config_line(PSTR("LongLength"), fwretract.settings.swap_retract_length, RET_STR);
+ // M11 Recover (undo) with swap (long) moves
+ config_line(SPEED_STR, fwretract.settings.retract_recover_feedrate_mm_s, UNRET_STR);
+ config_line(PSTR("ExtraLength"), fwretract.settings.retract_recover_extra, UNRET_STR);
+ config_line(PSTR("ExtraLongLength"), fwretract.settings.swap_retract_recover_extra, UNRET_STR);
+ config_line(PSTR("LongSpeed"), fwretract.settings.swap_retract_recover_feedrate_mm_s, UNRET_STR);
+ #endif
+
+ //
+ // Workspace boundaries
+ //
+ const xyz_pos_t dmin = { X_MIN_POS, Y_MIN_POS, Z_MIN_POS },
+ dmax = { X_MAX_POS, Y_MAX_POS, Z_MAX_POS };
+ xyz_pos_t cmin = dmin, cmax = dmax;
+ apply_motion_limits(cmin);
+ apply_motion_limits(cmax);
+ const xyz_pos_t wmin = cmin.asLogical(), wmax = cmax.asLogical();
+
+ PGMSTR(MIN_STR, "Min");
+ PGMSTR(MAX_STR, "Max");
+ PGMSTR(SIZE_STR, "Size");
+ config_line(MIN_STR, wmin.x, X_STR);
+ config_line(MIN_STR, wmin.y, Y_STR);
+ config_line(MIN_STR, wmin.z, Z_STR);
+ config_line(MAX_STR, wmax.x, X_STR);
+ config_line(MAX_STR, wmax.y, Y_STR);
+ config_line(MAX_STR, wmax.z, Z_STR);
+ config_line(SIZE_STR, wmax.x - wmin.x, X_STR);
+ config_line(SIZE_STR, wmax.y - wmin.y, Y_STR);
+ config_line(SIZE_STR, wmax.z - wmin.z, Z_STR);
+
+ //
+ // Print and Travel Acceleration
+ //
+ #define _ACCEL(A,B) _MIN(planner.settings.max_acceleration_mm_per_s2[A##_AXIS], planner.settings.B)
+ PGMSTR(P_ACC_STR, "PrintAccel");
+ PGMSTR(T_ACC_STR, "TravelAccel");
+ config_line(P_ACC_STR, _ACCEL(X, acceleration), X_STR);
+ config_line(P_ACC_STR, _ACCEL(Y, acceleration), Y_STR);
+ config_line(P_ACC_STR, _ACCEL(Z, acceleration), Z_STR);
+ config_line(T_ACC_STR, _ACCEL(X, travel_acceleration), X_STR);
+ config_line(T_ACC_STR, _ACCEL(Y, travel_acceleration), Y_STR);
+ config_line(T_ACC_STR, _ACCEL(Z, travel_acceleration), Z_STR);
+
+ config_prefix(PSTR("PrinterType"));
+ SERIAL_ECHOLNPGM(
+ TERN_(DELTA, "Delta")
+ TERN_(IS_SCARA, "SCARA")
+ TERN_(IS_CORE, "Core")
+ TERN_(MARKFORGED_XY, "MarkForged")
+ TERN_(IS_CARTESIAN, "Cartesian")
+ );
+
+ //
+ // Heated Bed
+ //
+ config_line(PSTR("HeatedBed"), ENABLED(HAS_HEATED_BED));
+ #if HAS_HEATED_BED
+ config_line(PSTR("MaxBedTemp"), BED_MAX_TARGET);
+ #endif
+
+ //
+ // Per-Extruder settings
+ //
+ config_line(PSTR("NumExtruder"), EXTRUDERS);
+ #if EXTRUDERS
+ LOOP_L_N(e, EXTRUDERS) {
+ config_line_e(e, JERK_STR, TERN(HAS_LINEAR_E_JERK, planner.max_e_jerk[E_INDEX_N(e)], TERN(HAS_CLASSIC_JERK, planner.max_jerk.e, DEFAULT_EJERK)));
+ config_line_e(e, PSTR("MaxSpeed"), planner.settings.max_feedrate_mm_s[E_AXIS_N(e)]);
+ config_line_e(e, PSTR("Acceleration"), planner.settings.max_acceleration_mm_per_s2[E_AXIS_N(e)]);
+ config_line_e(e, PSTR("Diameter"), TERN(NO_VOLUMETRICS, DEFAULT_NOMINAL_FILAMENT_DIA, planner.filament_size[e]));
+ config_line_e(e, PSTR("MaxTemp"), thermalManager.heater_maxtemp[e]);
+ }
+ #endif
+}
+
+#endif
diff --git a/Marlin/src/gcode/host/M876.cpp b/Marlin/src/gcode/host/M876.cpp
new file mode 100644
index 0000000..0d8256c
--- /dev/null
+++ b/Marlin/src/gcode/host/M876.cpp
@@ -0,0 +1,39 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(HOST_PROMPT_SUPPORT) && DISABLED(EMERGENCY_PARSER)
+
+#include "../../feature/host_actions.h"
+#include "../gcode.h"
+#include "../../MarlinCore.h"
+
+/**
+ * M876: Handle Prompt Response
+ */
+void GcodeSuite::M876() {
+
+ if (parser.seenval('S')) host_response_handler((uint8_t)parser.value_int());
+
+}
+
+#endif // HOST_PROMPT_SUPPORT && !EMERGENCY_PARSER
diff --git a/Marlin/src/gcode/lcd/M0_M1.cpp b/Marlin/src/gcode/lcd/M0_M1.cpp
new file mode 100644
index 0000000..414c4ce
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M0_M1.cpp
@@ -0,0 +1,87 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_RESUME_CONTINUE
+
+#include "../../inc/MarlinConfig.h"
+
+#include "../gcode.h"
+
+#include "../../module/planner.h" // for synchronize()
+#include "../../MarlinCore.h" // for wait_for_user_response()
+
+#if HAS_LCD_MENU
+ #include "../../lcd/marlinui.h"
+#elif ENABLED(EXTENSIBLE_UI)
+ #include "../../lcd/extui/ui_api.h"
+#endif
+
+#if ENABLED(HOST_PROMPT_SUPPORT)
+ #include "../../feature/host_actions.h"
+#endif
+
+/**
+ * M0: Unconditional stop - Wait for user button press on LCD
+ * M1: Conditional stop - Wait for user button press on LCD
+ */
+void GcodeSuite::M0_M1() {
+ millis_t ms = 0;
+ if (parser.seenval('P')) ms = parser.value_millis(); // Milliseconds to wait
+ if (parser.seenval('S')) ms = parser.value_millis_from_seconds(); // Seconds to wait
+
+ planner.synchronize();
+
+ #if HAS_LCD_MENU
+
+ if (parser.string_arg)
+ ui.set_status(parser.string_arg, true);
+ else {
+ LCD_MESSAGEPGM(MSG_USERWAIT);
+ #if ENABLED(LCD_PROGRESS_BAR) && PROGRESS_MSG_EXPIRE > 0
+ ui.reset_progress_bar_timeout();
+ #endif
+ }
+
+ #elif ENABLED(EXTENSIBLE_UI)
+ if (parser.string_arg)
+ ExtUI::onUserConfirmRequired(parser.string_arg); // Can this take an SRAM string??
+ else
+ ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_USERWAIT));
+ #else
+
+ if (parser.string_arg) {
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLN(parser.string_arg);
+ }
+
+ #endif
+
+ TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, parser.codenum ? PSTR("M1 Stop") : PSTR("M0 Stop"), CONTINUE_STR));
+
+ wait_for_user_response(ms);
+
+ TERN_(HAS_LCD_MENU, ui.reset_status());
+}
+
+#endif // HAS_RESUME_CONTINUE
diff --git a/Marlin/src/gcode/lcd/M117.cpp b/Marlin/src/gcode/lcd/M117.cpp
new file mode 100644
index 0000000..59305d9
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M117.cpp
@@ -0,0 +1,36 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M117: Set LCD Status Message
+ */
+void GcodeSuite::M117() {
+
+ if (parser.string_arg && parser.string_arg[0])
+ ui.set_status(parser.string_arg);
+ else
+ ui.reset_status();
+
+}
diff --git a/Marlin/src/gcode/lcd/M145.cpp b/Marlin/src/gcode/lcd/M145.cpp
new file mode 100644
index 0000000..84a7e75
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M145.cpp
@@ -0,0 +1,59 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if PREHEAT_COUNT
+
+#include "../gcode.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M145: Set the heatup state for a material in the LCD menu
+ *
+ * S<material>
+ * H<hotend temp>
+ * B<bed temp>
+ * F<fan speed>
+ */
+void GcodeSuite::M145() {
+ const uint8_t material = (uint8_t)parser.intval('S');
+ if (material >= PREHEAT_COUNT)
+ SERIAL_ERROR_MSG(STR_ERR_MATERIAL_INDEX);
+ else {
+ preheat_t &mat = ui.material_preset[material];
+ #if HAS_HOTEND
+ if (parser.seenval('H'))
+ mat.hotend_temp = constrain(parser.value_int(), EXTRUDE_MINTEMP, (HEATER_0_MAXTEMP) - (HOTEND_OVERSHOOT));
+ #endif
+ #if HAS_HEATED_BED
+ if (parser.seenval('B'))
+ mat.bed_temp = constrain(parser.value_int(), BED_MINTEMP, BED_MAX_TARGET);
+ #endif
+ #if HAS_FAN
+ if (parser.seenval('F'))
+ mat.fan_speed = constrain(parser.value_int(), 0, 255);
+ #endif
+ }
+}
+
+#endif // PREHEAT_COUNT
diff --git a/Marlin/src/gcode/lcd/M250.cpp b/Marlin/src/gcode/lcd/M250.cpp
new file mode 100644
index 0000000..f553044
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M250.cpp
@@ -0,0 +1,38 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_LCD_CONTRAST
+
+#include "../gcode.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M250: Read and optionally set the LCD contrast
+ */
+void GcodeSuite::M250() {
+ if (parser.seen('C')) ui.set_contrast(parser.value_int());
+ SERIAL_ECHOLNPAIR("LCD Contrast: ", ui.contrast);
+}
+
+#endif // HAS_LCD_CONTRAST
diff --git a/Marlin/src/gcode/lcd/M300.cpp b/Marlin/src/gcode/lcd/M300.cpp
new file mode 100644
index 0000000..5250774
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M300.cpp
@@ -0,0 +1,45 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_BUZZER
+
+#include "../gcode.h"
+
+#include "../../lcd/marlinui.h" // i2c-based BUZZ
+#include "../../libs/buzzer.h" // Buzzer, if possible
+
+/**
+ * M300: Play beep sound S<frequency Hz> P<duration ms>
+ */
+void GcodeSuite::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, 5000U);
+
+ BUZZ(duration, frequency);
+}
+
+#endif // HAS_BUZZER
diff --git a/Marlin/src/gcode/lcd/M414.cpp b/Marlin/src/gcode/lcd/M414.cpp
new file mode 100644
index 0000000..760028a
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M414.cpp
@@ -0,0 +1,44 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_MULTI_LANGUAGE
+
+#include "../gcode.h"
+#include "../../MarlinCore.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M414: Set the language for the UI
+ *
+ * Parameters
+ * S<index> : The language to select
+ */
+void GcodeSuite::M414() {
+
+ if (parser.seenval('S'))
+ ui.set_language(parser.value_byte());
+
+}
+
+#endif // HAS_MULTI_LANGUAGE
diff --git a/Marlin/src/gcode/lcd/M73.cpp b/Marlin/src/gcode/lcd/M73.cpp
new file mode 100644
index 0000000..5b135bd
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M73.cpp
@@ -0,0 +1,48 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(LCD_SET_PROGRESS_MANUALLY)
+
+#include "../gcode.h"
+#include "../../lcd/marlinui.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M73: Set percentage complete (for display on LCD)
+ *
+ * Example:
+ * M73 P25 ; Set progress to 25%
+ */
+void GcodeSuite::M73() {
+ if (parser.seen('P'))
+ ui.set_progress((PROGRESS_SCALE) > 1
+ ? parser.value_float() * (PROGRESS_SCALE)
+ : parser.value_byte()
+ );
+ #if BOTH(LCD_SET_PROGRESS_MANUALLY, USE_M73_REMAINING_TIME)
+ if (parser.seen('R')) ui.set_remaining_time(60 * parser.value_ulong());
+ #endif
+}
+
+#endif // LCD_SET_PROGRESS_MANUALLY
diff --git a/Marlin/src/gcode/lcd/M995.cpp b/Marlin/src/gcode/lcd/M995.cpp
new file mode 100644
index 0000000..bc8dc35
--- /dev/null
+++ b/Marlin/src/gcode/lcd/M995.cpp
@@ -0,0 +1,48 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(TOUCH_SCREEN_CALIBRATION)
+
+#include "../gcode.h"
+
+#if ENABLED(TFT_LVGL_UI)
+ #include "../../lcd/extui/lib/mks_ui/draw_touch_calibration.h"
+#else
+ #include "../../lcd/menu/menu.h"
+#endif
+
+/**
+ * M995: Touch screen calibration for TFT display
+ */
+void GcodeSuite::M995() {
+
+ #if ENABLED(TFT_LVGL_UI)
+ lv_draw_touch_calibration_screen();
+ #else
+ ui.goto_screen(touch_screen_calibration);
+ #endif
+
+}
+
+#endif // TOUCH_SCREEN_CALIBRATION
diff --git a/Marlin/src/gcode/motion/G0_G1.cpp b/Marlin/src/gcode/motion/G0_G1.cpp
new file mode 100644
index 0000000..9ac49bd
--- /dev/null
+++ b/Marlin/src/gcode/motion/G0_G1.cpp
@@ -0,0 +1,121 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+
+#include "../../MarlinCore.h"
+
+#if BOTH(FWRETRACT, FWRETRACT_AUTORETRACT)
+ #include "../../feature/fwretract.h"
+#endif
+
+#include "../../sd/cardreader.h"
+
+#if ENABLED(NANODLP_Z_SYNC)
+ #include "../../module/stepper.h"
+#endif
+
+extern xyze_pos_t destination;
+
+#if ENABLED(VARIABLE_G0_FEEDRATE)
+ feedRate_t fast_move_feedrate = MMM_TO_MMS(G0_FEEDRATE);
+#endif
+
+/**
+ * G0, G1: Coordinated movement of X Y Z E axes
+ */
+void GcodeSuite::G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move/*=false*/)) {
+
+ if (IsRunning()
+ #if ENABLED(NO_MOTION_BEFORE_HOMING)
+ && !homing_needed_error(
+ (parser.seen('X') ? _BV(X_AXIS) : 0)
+ | (parser.seen('Y') ? _BV(Y_AXIS) : 0)
+ | (parser.seen('Z') ? _BV(Z_AXIS) : 0) )
+ #endif
+ ) {
+
+ #ifdef G0_FEEDRATE
+ feedRate_t old_feedrate;
+ #if ENABLED(VARIABLE_G0_FEEDRATE)
+ if (fast_move) {
+ old_feedrate = feedrate_mm_s; // Back up the (old) motion mode feedrate
+ feedrate_mm_s = fast_move_feedrate; // Get G0 feedrate from last usage
+ }
+ #endif
+ #endif
+
+ get_destination_from_command(); // Get X Y Z E F (and set cutter power)
+
+ #ifdef G0_FEEDRATE
+ if (fast_move) {
+ #if ENABLED(VARIABLE_G0_FEEDRATE)
+ fast_move_feedrate = feedrate_mm_s; // Save feedrate for the next G0
+ #else
+ old_feedrate = feedrate_mm_s; // Back up the (new) motion mode feedrate
+ feedrate_mm_s = MMM_TO_MMS(G0_FEEDRATE); // Get the fixed G0 feedrate
+ #endif
+ }
+ #endif
+
+ #if BOTH(FWRETRACT, FWRETRACT_AUTORETRACT)
+
+ if (MIN_AUTORETRACT <= MAX_AUTORETRACT) {
+ // When M209 Autoretract is enabled, convert E-only moves to firmware retract/recover moves
+ if (fwretract.autoretract_enabled && parser.seen('E') && !(parser.seen('X') || parser.seen('Y') || parser.seen('Z'))) {
+ const float echange = destination.e - current_position.e;
+ // Is this a retract or recover move?
+ if (WITHIN(ABS(echange), MIN_AUTORETRACT, MAX_AUTORETRACT) && fwretract.retracted[active_extruder] == (echange > 0.0)) {
+ current_position.e = destination.e; // Hide a G1-based retract/recover from calculations
+ sync_plan_position_e(); // AND from the planner
+ return fwretract.retract(echange < 0.0); // Firmware-based retract/recover (double-retract ignored)
+ }
+ }
+ }
+
+ #endif // FWRETRACT
+
+ #if IS_SCARA
+ fast_move ? prepare_fast_move_to_destination() : prepare_line_to_destination();
+ #else
+ prepare_line_to_destination();
+ #endif
+
+ #ifdef G0_FEEDRATE
+ // Restore the motion mode feedrate
+ if (fast_move) feedrate_mm_s = old_feedrate;
+ #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) {
+ planner.synchronize();
+ SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP);
+ }
+ #endif
+ }
+}
diff --git a/Marlin/src/gcode/motion/G2_G3.cpp b/Marlin/src/gcode/motion/G2_G3.cpp
new file mode 100644
index 0000000..61e5024
--- /dev/null
+++ b/Marlin/src/gcode/motion/G2_G3.cpp
@@ -0,0 +1,370 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(ARC_SUPPORT)
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+#include "../../module/temperature.h"
+
+#if ENABLED(DELTA)
+ #include "../../module/delta.h"
+#elif ENABLED(SCARA)
+ #include "../../module/scara.h"
+#endif
+
+#if N_ARC_CORRECTION < 1
+ #undef N_ARC_CORRECTION
+ #define N_ARC_CORRECTION 1
+#endif
+
+/**
+ * Plan an arc in 2 dimensions, with optional linear motion in a 3rd dimension
+ *
+ * The arc is traced by generating many small linear segments, as configured by
+ * MM_PER_ARC_SEGMENT (Default 1mm). In the future we hope more slicers will include
+ * an option to generate G2/G3 arcs for curved surfaces, as this will allow faster
+ * boards to produce much smoother curved surfaces.
+ */
+void plan_arc(
+ const xyze_pos_t &cart, // Destination position
+ const ab_float_t &offset, // Center of rotation relative to current_position
+ const bool clockwise, // Clockwise?
+ const uint8_t circles // Take the scenic route
+) {
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ AxisEnum p_axis, q_axis, l_axis;
+ switch (gcode.workspace_plane) {
+ default:
+ case GcodeSuite::PLANE_XY: p_axis = X_AXIS; q_axis = Y_AXIS; l_axis = Z_AXIS; break;
+ case GcodeSuite::PLANE_YZ: p_axis = Y_AXIS; q_axis = Z_AXIS; l_axis = X_AXIS; break;
+ case GcodeSuite::PLANE_ZX: p_axis = Z_AXIS; q_axis = X_AXIS; l_axis = Y_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
+ ab_float_t rvec = -offset;
+
+ const float radius = HYPOT(rvec.a, rvec.b),
+ center_P = current_position[p_axis] - rvec.a,
+ center_Q = current_position[q_axis] - rvec.b,
+ rt_X = cart[p_axis] - center_P,
+ rt_Y = cart[q_axis] - center_Q,
+ start_L = current_position[l_axis];
+
+ #ifdef MIN_ARC_SEGMENTS
+ uint16_t min_segments = MIN_ARC_SEGMENTS;
+ #else
+ constexpr uint16_t min_segments = 1;
+ #endif
+
+ // Angle of rotation between position and target from the circle center.
+ float angular_travel;
+
+ // Do a full circle if starting and ending positions are "identical"
+ if (NEAR(current_position[p_axis], cart[p_axis]) && NEAR(current_position[q_axis], cart[q_axis])) {
+ // Preserve direction for circles
+ angular_travel = clockwise ? -RADIANS(360) : RADIANS(360);
+ }
+ else {
+ // Calculate the angle
+ angular_travel = ATAN2(rvec.a * rt_Y - rvec.b * rt_X, rvec.a * rt_X + rvec.b * rt_Y);
+
+ // Angular travel too small to detect? Just return.
+ if (!angular_travel) return;
+
+ // Make sure angular travel over 180 degrees goes the other way around.
+ switch (((angular_travel < 0) << 1) | clockwise) {
+ case 1: angular_travel -= RADIANS(360); break; // Positive but CW? Reverse direction.
+ case 2: angular_travel += RADIANS(360); break; // Negative but CCW? Reverse direction.
+ }
+
+ #ifdef MIN_ARC_SEGMENTS
+ min_segments = CEIL(min_segments * ABS(angular_travel) / RADIANS(360));
+ NOLESS(min_segments, 1U);
+ #endif
+ }
+
+ float linear_travel = cart[l_axis] - start_L,
+ extruder_travel = cart.e - current_position.e;
+
+ // If circling around...
+ if (ENABLED(ARC_P_CIRCLES) && circles) {
+ const float total_angular = angular_travel + circles * RADIANS(360), // Total rotation with all circles and remainder
+ part_per_circle = RADIANS(360) / total_angular, // Each circle's part of the total
+ l_per_circle = linear_travel * part_per_circle, // L movement per circle
+ e_per_circle = extruder_travel * part_per_circle; // E movement per circle
+ xyze_pos_t temp_position = current_position; // for plan_arc to compare to current_position
+ for (uint16_t n = circles; n--;) {
+ temp_position.e += e_per_circle; // Destination E axis
+ temp_position[l_axis] += l_per_circle; // Destination L axis
+ plan_arc(temp_position, offset, clockwise, 0); // Plan a single whole circle
+ }
+ linear_travel = cart[l_axis] - current_position[l_axis];
+ extruder_travel = cart.e - current_position.e;
+ }
+
+ const float flat_mm = radius * angular_travel,
+ mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : ABS(flat_mm);
+ if (mm_of_travel < 0.001f) return;
+
+ const feedRate_t scaled_fr_mm_s = MMS_SCALED(feedrate_mm_s);
+
+ // Start with a nominal segment length
+ float seg_length = (
+ #ifdef ARC_SEGMENTS_PER_R
+ constrain(MM_PER_ARC_SEGMENT * radius, MM_PER_ARC_SEGMENT, ARC_SEGMENTS_PER_R)
+ #elif ARC_SEGMENTS_PER_SEC
+ _MAX(scaled_fr_mm_s * RECIPROCAL(ARC_SEGMENTS_PER_SEC), MM_PER_ARC_SEGMENT)
+ #else
+ MM_PER_ARC_SEGMENT
+ #endif
+ );
+ // Divide total travel by nominal segment length
+ uint16_t segments = FLOOR(mm_of_travel / seg_length);
+ NOLESS(segments, min_segments); // At least some segments
+ seg_length = mm_of_travel / segments;
+
+ /**
+ * 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
+ xyze_pos_t raw;
+ const float theta_per_segment = angular_travel / segments,
+ linear_per_segment = linear_travel / segments,
+ extruder_per_segment = extruder_travel / segments,
+ sq_theta_per_segment = sq(theta_per_segment),
+ sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
+ cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
+
+ // Initialize the linear axis
+ raw[l_axis] = current_position[l_axis];
+
+ // Initialize the extruder axis
+ raw.e = current_position.e;
+
+ #if ENABLED(SCARA_FEEDRATE_SCALING)
+ const float inv_duration = scaled_fr_mm_s / seg_length;
+ #endif
+
+ 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 rvec.a / 1
+ const float r_new_Y = rvec.a * sin_T + rvec.b * cos_T;
+ rvec.a = rvec.a * cos_T - rvec.b * sin_T;
+ rvec.b = 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);
+ rvec.a = -offset[0] * cos_Ti + offset[1] * sin_Ti;
+ rvec.b = -offset[0] * sin_Ti - offset[1] * cos_Ti;
+ }
+
+ // Update raw location
+ raw[p_axis] = center_P + rvec.a;
+ raw[q_axis] = center_Q + rvec.b;
+ #if ENABLED(AUTO_BED_LEVELING_UBL)
+ raw[l_axis] = start_L;
+ UNUSED(linear_per_segment);
+ #else
+ raw[l_axis] += linear_per_segment;
+ #endif
+ raw.e += extruder_per_segment;
+
+ apply_motion_limits(raw);
+
+ #if HAS_LEVELING && !PLANNER_LEVELING
+ planner.apply_leveling(raw);
+ #endif
+
+ if (!planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, 0
+ #if ENABLED(SCARA_FEEDRATE_SCALING)
+ , inv_duration
+ #endif
+ )) break;
+ }
+
+ // Ensure last segment arrives at target location.
+ raw = cart;
+ TERN_(AUTO_BED_LEVELING_UBL, raw[l_axis] = start_L);
+
+ apply_motion_limits(raw);
+
+ #if HAS_LEVELING && !PLANNER_LEVELING
+ planner.apply_leveling(raw);
+ #endif
+
+ planner.buffer_line(raw, scaled_fr_mm_s, active_extruder, 0
+ #if ENABLED(SCARA_FEEDRATE_SCALING)
+ , inv_duration
+ #endif
+ );
+
+ TERN_(AUTO_BED_LEVELING_UBL, raw[l_axis] = start_L);
+ current_position = raw;
+
+} // plan_arc
+
+/**
+ * G2: Clockwise Arc
+ * G3: Counterclockwise Arc
+ *
+ * This command has two forms: IJ-form (JK, KI) and R-form.
+ *
+ * - Depending on the current Workspace Plane orientation,
+ * use parameters IJ/JK/KI to specify the XY/YZ/ZX offsets.
+ * At least one of the IJ/JK/KI parameters is required.
+ * XY/YZ/ZX can be omitted to do a complete circle.
+ * The given XY/YZ/ZX is not error-checked. The arc ends
+ * based on the angle of the destination.
+ * Mixing IJ/JK/KI with R will throw an error.
+ *
+ * - R specifies the radius. X or Y (Y or Z / Z or X) is required.
+ * Omitting both XY/YZ/ZX will throw an error.
+ * XY/YZ/ZX must differ from the current XY/YZ/ZX.
+ * Mixing R with IJ/JK/KI 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
+ */
+void GcodeSuite::G2_G3(const bool clockwise) {
+ if (MOTION_CONDITIONS) {
+
+ #if ENABLED(SF_ARC_FIX)
+ const bool relative_mode_backup = relative_mode;
+ relative_mode = true;
+ #endif
+
+ get_destination_from_command(); // Get X Y Z E F (and set cutter power)
+
+ TERN_(SF_ARC_FIX, relative_mode = relative_mode_backup);
+
+ ab_float_t arc_offset = { 0, 0 };
+ if (parser.seenval('R')) {
+ const float r = parser.value_linear_units();
+ if (r) {
+ const xy_pos_t p1 = current_position, p2 = destination;
+ if (p1 != p2) {
+ const xy_pos_t d2 = (p2 - p1) * 0.5f; // XY vector to midpoint of move from current
+ const float e = clockwise ^ (r < 0) ? -1 : 1, // clockwise -1/1, counterclockwise 1/-1
+ len = d2.magnitude(), // Distance to mid-point of move from current
+ h2 = (r - len) * (r + len), // factored to reduce rounding error
+ h = (h2 >= 0) ? SQRT(h2) : 0.0f; // Distance to the arc pivot-point from midpoint
+ const xy_pos_t s = { -d2.y, d2.x }; // Perpendicular bisector. (Divide by len for unit vector.)
+ arc_offset = d2 + s / len * e * h; // The calculated offset (mid-point if |r| <= len)
+ }
+ }
+ }
+ else {
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ char achar, bchar;
+ switch (gcode.workspace_plane) {
+ default:
+ case GcodeSuite::PLANE_XY: achar = 'I'; bchar = 'J'; break;
+ case GcodeSuite::PLANE_YZ: achar = 'J'; bchar = 'K'; break;
+ case GcodeSuite::PLANE_ZX: achar = 'K'; bchar = 'I'; break;
+ }
+ #else
+ constexpr char achar = 'I', bchar = 'J';
+ #endif
+ if (parser.seenval(achar)) arc_offset.a = parser.value_linear_units();
+ if (parser.seenval(bchar)) arc_offset.b = parser.value_linear_units();
+ }
+
+ if (arc_offset) {
+
+ #if ENABLED(ARC_P_CIRCLES)
+ // P indicates number of circles to do
+ const int8_t circles_to_do = parser.byteval('P');
+ if (!WITHIN(circles_to_do, 0, 100))
+ SERIAL_ERROR_MSG(STR_ERR_ARC_ARGS);
+ #else
+ constexpr uint8_t circles_to_do = 0;
+ #endif
+
+ // Send the arc to the planner
+ plan_arc(destination, arc_offset, clockwise, circles_to_do);
+ reset_stepper_timeout();
+ }
+ else
+ SERIAL_ERROR_MSG(STR_ERR_ARC_ARGS);
+ }
+}
+
+#endif // ARC_SUPPORT
diff --git a/Marlin/src/gcode/motion/G4.cpp b/Marlin/src/gcode/motion/G4.cpp
new file mode 100644
index 0000000..724ed7f
--- /dev/null
+++ b/Marlin/src/gcode/motion/G4.cpp
@@ -0,0 +1,44 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/stepper.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * G4: Dwell S<seconds> or P<milliseconds>
+ */
+void GcodeSuite::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
+
+ planner.synchronize();
+ #if ENABLED(NANODLP_Z_SYNC)
+ SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP);
+ #endif
+
+ if (!ui.has_status()) LCD_MESSAGEPGM(MSG_DWELL);
+
+ dwell(dwell_ms);
+}
diff --git a/Marlin/src/gcode/motion/G5.cpp b/Marlin/src/gcode/motion/G5.cpp
new file mode 100644
index 0000000..2c98fae
--- /dev/null
+++ b/Marlin/src/gcode/motion/G5.cpp
@@ -0,0 +1,65 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(BEZIER_CURVE_SUPPORT)
+
+#include "../../module/motion.h"
+#include "../../module/planner_bezier.h"
+
+/**
+ * Parameters interpreted according to:
+ * https://linuxcnc.org/docs/2.7/html/gcode/g-code.html#gcode:g5
+ * However I, J omission is not supported at this point; all
+ * parameters can be omitted and default to zero.
+ */
+
+#include "../gcode.h"
+#include "../../MarlinCore.h" // for IsRunning()
+
+/**
+ * G5: Cubic B-spline
+ */
+void GcodeSuite::G5() {
+ if (MOTION_CONDITIONS) {
+
+ #if ENABLED(CNC_WORKSPACE_PLANES)
+ if (workspace_plane != PLANE_XY) {
+ SERIAL_ERROR_MSG(STR_ERR_BAD_PLANE_MODE);
+ return;
+ }
+ #endif
+
+ get_destination_from_command();
+
+ const xy_pos_t offsets[2] = {
+ { parser.linearval('I'), parser.linearval('J') },
+ { parser.linearval('P'), parser.linearval('Q') }
+ };
+
+ cubic_b_spline(current_position, destination, offsets, MMS_SCALED(feedrate_mm_s), active_extruder);
+ current_position = destination;
+ }
+}
+
+#endif // BEZIER_CURVE_SUPPORT
diff --git a/Marlin/src/gcode/motion/G6.cpp b/Marlin/src/gcode/motion/G6.cpp
new file mode 100644
index 0000000..168dc28
--- /dev/null
+++ b/Marlin/src/gcode/motion/G6.cpp
@@ -0,0 +1,61 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(DIRECT_STEPPING)
+
+#include "../../feature/direct_stepping.h"
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+
+/**
+ * G6: Direct Stepper Move
+ */
+void GcodeSuite::G6() {
+ // TODO: feedrate support?
+ if (parser.seen('R'))
+ planner.last_page_step_rate = parser.value_ulong();
+
+ if (!DirectStepping::Config::DIRECTIONAL) {
+ if (parser.seen('X')) planner.last_page_dir.x = !!parser.value_byte();
+ if (parser.seen('Y')) planner.last_page_dir.y = !!parser.value_byte();
+ if (parser.seen('Z')) planner.last_page_dir.z = !!parser.value_byte();
+ if (parser.seen('E')) planner.last_page_dir.e = !!parser.value_byte();
+ }
+
+ // No index means we just set the state
+ if (!parser.seen('I')) return;
+
+ // No speed is set, can't schedule the move
+ if (!planner.last_page_step_rate) return;
+
+ const page_idx_t page_idx = (page_idx_t) parser.value_ulong();
+
+ uint16_t num_steps = DirectStepping::Config::TOTAL_STEPS;
+ if (parser.seen('S')) num_steps = parser.value_ushort();
+
+ planner.buffer_page(page_idx, 0, num_steps);
+ reset_stepper_timeout();
+}
+
+#endif // DIRECT_STEPPING
diff --git a/Marlin/src/gcode/motion/G80.cpp b/Marlin/src/gcode/motion/G80.cpp
new file mode 100644
index 0000000..f674596
--- /dev/null
+++ b/Marlin/src/gcode/motion/G80.cpp
@@ -0,0 +1,38 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(GCODE_MOTION_MODES)
+
+#include "../gcode.h"
+
+/**
+ * G80: Cancel current motion mode
+ */
+void GcodeSuite::G80() {
+
+ parser.cancel_motion_mode();
+
+}
+
+#endif // GCODE_MOTION_MODES
diff --git a/Marlin/src/gcode/motion/M290.cpp b/Marlin/src/gcode/motion/M290.cpp
new file mode 100644
index 0000000..df8dad7
--- /dev/null
+++ b/Marlin/src/gcode/motion/M290.cpp
@@ -0,0 +1,136 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(BABYSTEPPING)
+
+#include "../gcode.h"
+#include "../../feature/babystep.h"
+#include "../../module/probe.h"
+#include "../../module/planner.h"
+
+#if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ #include "../../core/serial.h"
+#endif
+
+#if ENABLED(MESH_BED_LEVELING)
+ #include "../../feature/bedlevel/bedlevel.h"
+#endif
+
+#if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+
+ FORCE_INLINE void mod_probe_offset(const float &offs) {
+ if (TERN1(BABYSTEP_HOTEND_Z_OFFSET, active_extruder == 0)) {
+ probe.offset.z += offs;
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(STR_PROBE_OFFSET " " STR_Z, probe.offset.z);
+ }
+ else {
+ #if ENABLED(BABYSTEP_HOTEND_Z_OFFSET)
+ hotend_offset[active_extruder].z -= offs;
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR(STR_PROBE_OFFSET STR_Z ": ", hotend_offset[active_extruder].z);
+ #endif
+ }
+ }
+
+#endif
+
+/**
+ * M290: Babystepping
+ *
+ * Send 'R' or no parameters for a report.
+ *
+ * X<linear> - Distance to step X
+ * Y<linear> - Distance to step Y
+ * Z<linear> - Distance to step Z
+ * S<linear> - Distance to step Z (alias for Z)
+ *
+ * With BABYSTEP_ZPROBE_OFFSET:
+ * P0 - Don't adjust the Z probe offset
+ */
+void GcodeSuite::M290() {
+ #if ENABLED(BABYSTEP_XY)
+ LOOP_XYZ(a)
+ if (parser.seenval(XYZ_CHAR(a)) || (a == Z_AXIS && parser.seenval('S'))) {
+ const float offs = constrain(parser.value_axis_units((AxisEnum)a), -2, 2);
+ babystep.add_mm((AxisEnum)a, offs);
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ if (a == Z_AXIS && (!parser.seen('P') || parser.value_bool())) mod_probe_offset(offs);
+ #endif
+ }
+ #else
+ if (parser.seenval('Z') || parser.seenval('S')) {
+ const float offs = constrain(parser.value_axis_units(Z_AXIS), -2, 2);
+ babystep.add_mm(Z_AXIS, offs);
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ if (!parser.seen('P') || parser.value_bool()) mod_probe_offset(offs);
+ #endif
+ }
+ #endif
+
+ if (!parser.seen("XYZ") || parser.seen('R')) {
+ SERIAL_ECHO_START();
+
+ #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
+ SERIAL_ECHOLNPAIR(STR_PROBE_OFFSET " " STR_Z, probe.offset.z);
+ #endif
+
+ #if ENABLED(BABYSTEP_HOTEND_Z_OFFSET)
+ {
+ SERIAL_ECHOLNPAIR_P(
+ PSTR("Hotend "), int(active_extruder)
+ #if ENABLED(BABYSTEP_XY)
+ , PSTR("Offset X"), hotend_offset[active_extruder].x
+ , SP_Y_STR, hotend_offset[active_extruder].y
+ , SP_Z_STR
+ #else
+ , PSTR("Offset Z")
+ #endif
+ , hotend_offset[active_extruder].z
+ );
+ }
+ #endif
+
+ #if ENABLED(MESH_BED_LEVELING)
+ SERIAL_ECHOLNPAIR("MBL Adjust Z", mbl.z_offset);
+ #endif
+
+ #if ENABLED(BABYSTEP_DISPLAY_TOTAL)
+ {
+ SERIAL_ECHOLNPAIR_P(
+ #if ENABLED(BABYSTEP_XY)
+ PSTR("Babystep X"), babystep.axis_total[X_AXIS]
+ , SP_Y_STR, babystep.axis_total[Y_AXIS]
+ , SP_Z_STR
+ #else
+ PSTR("Babystep Z")
+ #endif
+ , babystep.axis_total[BS_TOTAL_IND(Z_AXIS)]
+ );
+ }
+ #endif
+ }
+}
+
+#endif // BABYSTEPPING
diff --git a/Marlin/src/gcode/parser.cpp b/Marlin/src/gcode/parser.cpp
new file mode 100644
index 0000000..a513c4b
--- /dev/null
+++ b/Marlin/src/gcode/parser.cpp
@@ -0,0 +1,406 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * parser.cpp - Parser for a GCode line, providing a parameter interface.
+ */
+
+#include "parser.h"
+
+#include "../MarlinCore.h"
+
+// Must be declared for allocation and to satisfy the linker
+// Zero values need no initialization.
+
+bool GCodeParser::volumetric_enabled;
+
+#if ENABLED(INCH_MODE_SUPPORT)
+ float GCodeParser::linear_unit_factor, GCodeParser::volumetric_unit_factor;
+#endif
+
+#if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+ TempUnit GCodeParser::input_temp_units = TEMPUNIT_C;
+#endif
+
+char *GCodeParser::command_ptr,
+ *GCodeParser::string_arg,
+ *GCodeParser::value_ptr;
+char GCodeParser::command_letter;
+uint16_t GCodeParser::codenum;
+
+#if ENABLED(USE_GCODE_SUBCODES)
+ uint8_t GCodeParser::subcode;
+#endif
+
+#if ENABLED(GCODE_MOTION_MODES)
+ int16_t GCodeParser::motion_mode_codenum = -1;
+ #if ENABLED(USE_GCODE_SUBCODES)
+ uint8_t GCodeParser::motion_mode_subcode;
+ #endif
+#endif
+
+#if ENABLED(FASTER_GCODE_PARSER)
+ // Optimized Parameters
+ uint32_t GCodeParser::codebits; // found bits
+ uint8_t GCodeParser::param[26]; // parameter offsets from command_ptr
+#else
+ char *GCodeParser::command_args; // start of parameters
+#endif
+
+// Create a global instance of the GCode parser singleton
+GCodeParser parser;
+
+/**
+ * Clear all code-seen (and value pointers)
+ *
+ * Since each param is set/cleared on seen codes,
+ * this may be optimized by commenting out ZERO(param)
+ */
+void GCodeParser::reset() {
+ string_arg = nullptr; // No whole line argument
+ command_letter = '?'; // No command letter
+ codenum = 0; // No command code
+ TERN_(USE_GCODE_SUBCODES, subcode = 0); // No command sub-code
+ #if ENABLED(FASTER_GCODE_PARSER)
+ codebits = 0; // No codes yet
+ //ZERO(param); // No parameters (should be safe to comment out this line)
+ #endif
+}
+
+#if ENABLED(GCODE_QUOTED_STRINGS)
+
+ // Pass the address after the first quote (if any)
+ char* GCodeParser::unescape_string(char* &src) {
+ if (*src == '"') ++src; // Skip the leading quote
+ char * const out = src; // Start of the string
+ char *dst = src; // Prepare to unescape and terminate
+ for (;;) {
+ char c = *src++; // Get the next char
+ switch (c) {
+ case '\\': c = *src++; break; // Get the escaped char
+ case '"' : c = '\0'; break; // Convert bare quote to nul
+ }
+ if (!(*dst++ = c)) break; // Copy and break on nul
+ }
+ return out;
+ }
+
+#endif
+
+// Populate all fields by parsing a single line of GCode
+// 58 bytes of SRAM are used to speed up seen/value
+void GCodeParser::parse(char *p) {
+
+ reset(); // No codes to report
+
+ auto uppercase = [](char c) {
+ if (TERN0(GCODE_CASE_INSENSITIVE, WITHIN(c, 'a', 'z')))
+ c += 'A' - 'a';
+ return c;
+ };
+
+ // Skip spaces
+ while (*p == ' ') ++p;
+
+ // Skip N[-0-9] if included in the command line
+ if (uppercase(*p) == 'N' && NUMERIC_SIGNED(p[1])) {
+ //TERN_(FASTER_GCODE_PARSER, set('N', p + 1)); // (optional) Set the 'N' parameter value
+ p += 2; // skip N[-0-9]
+ while (NUMERIC(*p)) ++p; // skip [0-9]*
+ while (*p == ' ') ++p; // skip [ ]*
+ }
+
+ // *p now points to the current command, which should be G, M, or T
+ command_ptr = p;
+
+ // Get the command letter, which must be G, M, or T
+ const char letter = uppercase(*p++);
+
+ // Nullify asterisk and trailing whitespace
+ char *starpos = strchr(p, '*');
+ if (starpos) {
+ --starpos; // *
+ while (*starpos == ' ') --starpos; // spaces...
+ starpos[1] = '\0';
+ }
+
+ #if ANY(MARLIN_DEV_MODE, SWITCHING_TOOLHEAD, MAGNETIC_SWITCHING_TOOLHEAD, ELECTROMAGNETIC_SWITCHING_TOOLHEAD)
+ #define SIGNED_CODENUM 1
+ #endif
+
+ // Bail if the letter is not G, M, or T
+ // (or a valid parameter for the current motion mode)
+ switch (letter) {
+
+ case 'G': case 'M': case 'T': TERN_(MARLIN_DEV_MODE, case 'D':)
+ // Skip spaces to get the numeric part
+ while (*p == ' ') p++;
+
+ #if HAS_PRUSA_MMU2
+ if (letter == 'T') {
+ // check for special MMU2 T?/Tx/Tc commands
+ if (*p == '?' || *p == 'x' || *p == 'c') {
+ command_letter = letter;
+ string_arg = p;
+ return;
+ }
+ }
+ #endif
+
+ // Bail if there's no command code number
+ if (!TERN(SIGNED_CODENUM, NUMERIC_SIGNED(*p), NUMERIC(*p))) return;
+
+ // Save the command letter at this point
+ // A '?' signifies an unknown command
+ command_letter = letter;
+
+ {
+ #if ENABLED(SIGNED_CODENUM)
+ int sign = 1; // Allow for a negative code like D-1 or T-1
+ if (*p == '-') { sign = -1; ++p; }
+ #endif
+
+ // Get the code number - integer digits only
+ codenum = 0;
+
+ do { codenum = codenum * 10 + *p++ - '0'; } while (NUMERIC(*p));
+
+ // Apply the sign, if any
+ TERN_(SIGNED_CODENUM, codenum *= sign);
+ }
+
+ // Allow for decimal point in command
+ #if ENABLED(USE_GCODE_SUBCODES)
+ if (*p == '.') {
+ p++;
+ while (NUMERIC(*p))
+ subcode = subcode * 10 + *p++ - '0';
+ }
+ #endif
+
+ // Skip all spaces to get to the first argument, or nul
+ while (*p == ' ') p++;
+
+ #if ENABLED(GCODE_MOTION_MODES)
+ if (letter == 'G'
+ && (codenum <= TERN(ARC_SUPPORT, 3, 1) || codenum == 5 || TERN0(G38_PROBE_TARGET, codenum == 38))
+ ) {
+ motion_mode_codenum = codenum;
+ TERN_(USE_GCODE_SUBCODES, motion_mode_subcode = subcode);
+ }
+ #endif
+
+ break;
+
+ #if ENABLED(GCODE_MOTION_MODES)
+ #if ENABLED(ARC_SUPPORT)
+ case 'I' ... 'J': case 'R':
+ if (motion_mode_codenum != 2 && motion_mode_codenum != 3) return;
+ #endif
+ case 'P' ... 'Q':
+ if (motion_mode_codenum != 5) return;
+ case 'X' ... 'Z': case 'E' ... 'F':
+ if (motion_mode_codenum < 0) return;
+ command_letter = 'G';
+ codenum = motion_mode_codenum;
+ TERN_(USE_GCODE_SUBCODES, subcode = motion_mode_subcode);
+ p--; // Back up one character to use the current parameter
+ break;
+ #endif // GCODE_MOTION_MODES
+
+ default: return;
+ }
+
+ // The command parameters (if any) start here, for sure!
+
+ #if DISABLED(FASTER_GCODE_PARSER)
+ command_args = p; // Scan for parameters in seen()
+ #endif
+
+ // Only use string_arg for these M codes
+ if (letter == 'M') switch (codenum) {
+ #if ENABLED(GCODE_MACROS)
+ case 810 ... 819:
+ #endif
+ #if ENABLED(EXPECTED_PRINTER_CHECK)
+ case 16:
+ #endif
+ case 23: case 28: case 30: case 117 ... 118: case 928:
+ string_arg = unescape_string(p);
+ return;
+ default: break;
+ }
+
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ const bool debug = codenum == 800;
+ #endif
+
+ /**
+ * Find all parameters, set flags and pointers for fast parsing
+ *
+ * Most codes ignore 'string_arg', but those that want a string will get the right pointer.
+ * The following loop assigns the first "parameter" having no numeric value to 'string_arg'.
+ * This allows M0/M1 with expire time to work: "M0 S5 You Win!"
+ * For 'M118' you must use 'E1' and 'A1' rather than just 'E' or 'A'
+ */
+ #if ENABLED(GCODE_QUOTED_STRINGS)
+ bool quoted_string_arg = false;
+ #endif
+ string_arg = nullptr;
+ while (const char param = uppercase(*p++)) { // Get the next parameter. A NUL ends the loop
+
+ // Special handling for M32 [P] !/path/to/file.g#
+ // The path must be the last parameter
+ if (param == '!' && is_command('M', 32)) {
+ string_arg = p; // Name starts after '!'
+ char * const lb = strchr(p, '#'); // Already seen '#' as SD char (to pause buffering)
+ if (lb) *lb = '\0'; // Safe to mark the end of the filename
+ return;
+ }
+
+ #if ENABLED(GCODE_QUOTED_STRINGS)
+ if (!quoted_string_arg && param == '"') {
+ quoted_string_arg = true;
+ string_arg = unescape_string(p);
+ }
+ #endif
+
+ #if ENABLED(FASTER_GCODE_PARSER)
+ // Arguments MUST be uppercase for fast GCode parsing
+ #define PARAM_OK(P) WITHIN((P), 'A', 'Z')
+ #else
+ #define PARAM_OK(P) true
+ #endif
+
+ if (PARAM_OK(param)) {
+
+ while (*p == ' ') p++; // Skip spaces between parameters & values
+
+ #if ENABLED(GCODE_QUOTED_STRINGS)
+ const bool is_str = (*p == '"'), has_val = is_str || valid_float(p);
+ char * const valptr = has_val ? is_str ? unescape_string(p) : p : nullptr;
+ #else
+ const bool has_val = valid_float(p);
+ #if ENABLED(FASTER_GCODE_PARSER)
+ char * const valptr = has_val ? p : nullptr;
+ #endif
+ #endif
+
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ if (debug) {
+ SERIAL_ECHOPAIR("Got param ", param, " at index ", (int)(p - command_ptr - 1));
+ if (has_val) SERIAL_ECHOPGM(" (has_val)");
+ }
+ #endif
+
+ if (!has_val && !string_arg) { // No value? First time, keep as string_arg
+ string_arg = p - 1;
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG
+ #endif
+ }
+
+ if (TERN0(DEBUG_GCODE_PARSER, debug)) SERIAL_EOL();
+
+ TERN_(FASTER_GCODE_PARSER, set(param, valptr)); // Set parameter exists and pointer (nullptr for no value)
+ }
+ else if (!string_arg) { // Not A-Z? First time, keep as the string_arg
+ string_arg = p - 1;
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ if (debug) SERIAL_ECHOPAIR(" string_arg: ", hex_address((void*)string_arg)); // DEBUG
+ #endif
+ }
+
+ if (!WITHIN(*p, 'A', 'Z')) { // Another parameter right away?
+ while (*p && DECIMAL_SIGNED(*p)) p++; // Skip over the value section of a parameter
+ while (*p == ' ') p++; // Skip over all spaces
+ }
+ }
+}
+
+#if ENABLED(CNC_COORDINATE_SYSTEMS)
+
+ // Parse the next parameter as a new command
+ bool GCodeParser::chain() {
+ #if ENABLED(FASTER_GCODE_PARSER)
+ char *next_command = command_ptr;
+ if (next_command) {
+ while (*next_command && *next_command != ' ') ++next_command;
+ while (*next_command == ' ') ++next_command;
+ if (!*next_command) next_command = nullptr;
+ }
+ #else
+ const char *next_command = command_args;
+ #endif
+ if (next_command) parse(next_command);
+ return !!next_command;
+ }
+
+#endif // CNC_COORDINATE_SYSTEMS
+
+void GCodeParser::unknown_command_warning() {
+ SERIAL_ECHO_MSG(STR_UNKNOWN_COMMAND, command_ptr, "\"");
+}
+
+#if ENABLED(DEBUG_GCODE_PARSER)
+
+ void GCodeParser::debug() {
+ SERIAL_ECHOPAIR("Command: ", command_ptr, " (", command_letter);
+ SERIAL_ECHO(codenum);
+ SERIAL_ECHOLNPGM(")");
+ #if ENABLED(FASTER_GCODE_PARSER)
+ SERIAL_ECHOPGM(" args: { ");
+ for (char c = 'A'; c <= 'Z'; ++c) if (seen(c)) SERIAL_CHAR(c, ' ');
+ SERIAL_CHAR('}');
+ #else
+ SERIAL_ECHOPAIR(" args: { ", command_args, " }");
+ #endif
+ if (string_arg) {
+ SERIAL_ECHOPAIR(" string: \"", string_arg);
+ SERIAL_CHAR('"');
+ }
+ SERIAL_ECHOLNPGM("\n");
+ for (char c = 'A'; c <= 'Z'; ++c) {
+ if (seen(c)) {
+ SERIAL_ECHOPAIR("Code '", c); SERIAL_ECHOPGM("':");
+ if (has_value()) {
+ SERIAL_ECHOLNPAIR(
+ "\n float: ", value_float(),
+ "\n long: ", value_long(),
+ "\n ulong: ", value_ulong(),
+ "\n millis: ", value_millis(),
+ "\n sec-ms: ", value_millis_from_seconds(),
+ "\n int: ", value_int(),
+ "\n ushort: ", value_ushort(),
+ "\n byte: ", (int)value_byte(),
+ "\n bool: ", (int)value_bool(),
+ "\n linear: ", value_linear_units(),
+ "\n celsius: ", value_celsius()
+ );
+ }
+ else
+ SERIAL_ECHOLNPGM(" (no value)");
+ }
+ }
+ }
+
+#endif // DEBUG_GCODE_PARSER
diff --git a/Marlin/src/gcode/parser.h b/Marlin/src/gcode/parser.h
new file mode 100644
index 0000000..cf531c4
--- /dev/null
+++ b/Marlin/src/gcode/parser.h
@@ -0,0 +1,441 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+/**
+ * parser.h - Parser for a GCode line, providing a parameter interface.
+ * Codes like M149 control the way the GCode parser behaves,
+ * so settings for these codes are located in this class.
+ */
+
+#include "../inc/MarlinConfig.h"
+
+//#define DEBUG_GCODE_PARSER
+#if ENABLED(DEBUG_GCODE_PARSER)
+ #include "../libs/hex_print.h"
+#endif
+
+#if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+ typedef enum : uint8_t { TEMPUNIT_C, TEMPUNIT_K, TEMPUNIT_F } TempUnit;
+#endif
+
+#if ENABLED(INCH_MODE_SUPPORT)
+ typedef enum : uint8_t { LINEARUNIT_MM, LINEARUNIT_INCH } LinearUnit;
+#endif
+
+/**
+ * GCode parser
+ *
+ * - Parse a single gcode line for its letter, code, subcode, and parameters
+ * - FASTER_GCODE_PARSER:
+ * - Flags existing params (1 bit each)
+ * - Stores value offsets (1 byte each)
+ * - Provide accessors for parameters:
+ * - Parameter exists
+ * - Parameter has value
+ * - Parameter value in different units and types
+ */
+class GCodeParser {
+
+private:
+ static char *value_ptr; // Set by seen, used to fetch the value
+
+ #if ENABLED(FASTER_GCODE_PARSER)
+ static uint32_t codebits; // Parameters pre-scanned
+ static uint8_t param[26]; // For A-Z, offsets into command args
+ #else
+ static char *command_args; // Args start here, for slow scan
+ #endif
+
+public:
+
+ // Global states for GCode-level units features
+
+ static bool volumetric_enabled;
+
+ #if ENABLED(INCH_MODE_SUPPORT)
+ static float linear_unit_factor, volumetric_unit_factor;
+ #endif
+
+ #if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+ static TempUnit input_temp_units;
+ #endif
+
+ // Command line state
+ static char *command_ptr, // The command, so it can be echoed
+ *string_arg, // string of command line
+ command_letter; // G, M, or T
+ static uint16_t codenum; // 123
+ #if ENABLED(USE_GCODE_SUBCODES)
+ static uint8_t subcode; // .1
+ #endif
+
+ #if ENABLED(GCODE_MOTION_MODES)
+ static int16_t motion_mode_codenum;
+ #if ENABLED(USE_GCODE_SUBCODES)
+ static uint8_t motion_mode_subcode;
+ #endif
+ FORCE_INLINE static void cancel_motion_mode() { motion_mode_codenum = -1; }
+ #endif
+
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ static void debug();
+ #endif
+
+ // Reset is done before parsing
+ static void reset();
+
+ #define LETTER_BIT(N) ((N) - 'A')
+
+ FORCE_INLINE static bool valid_signless(const char * const p) {
+ return NUMERIC(p[0]) || (p[0] == '.' && NUMERIC(p[1])); // .?[0-9]
+ }
+
+ FORCE_INLINE static bool valid_float(const char * const p) {
+ return valid_signless(p) || ((p[0] == '-' || p[0] == '+') && valid_signless(&p[1])); // [-+]?.?[0-9]
+ }
+
+ FORCE_INLINE static bool valid_number(const char * const p) {
+ // TODO: With MARLIN_DEV_MODE allow HEX values starting with "x"
+ return valid_float(p);
+ }
+
+ #if ENABLED(FASTER_GCODE_PARSER)
+
+ FORCE_INLINE static bool valid_int(const char * const p) {
+ return NUMERIC(p[0]) || ((p[0] == '-' || p[0] == '+') && NUMERIC(p[1])); // [-+]?[0-9]
+ }
+
+ // Set the flag and pointer for a parameter
+ static inline void set(const char c, char * const ptr) {
+ const uint8_t ind = LETTER_BIT(c);
+ if (ind >= COUNT(param)) return; // Only A-Z
+ SBI32(codebits, ind); // parameter exists
+ param[ind] = ptr ? ptr - command_ptr : 0; // parameter offset or 0
+ #if ENABLED(DEBUG_GCODE_PARSER)
+ if (codenum == 800) {
+ SERIAL_ECHOPAIR("Set bit ", (int)ind, " of codebits (", hex_address((void*)(codebits >> 16)));
+ print_hex_word((uint16_t)(codebits & 0xFFFF));
+ SERIAL_ECHOLNPAIR(") | param = ", (int)param[ind]);
+ }
+ #endif
+ }
+
+ // Code seen bit was set. If not found, value_ptr is unchanged.
+ // This allows "if (seen('A')||seen('B'))" to use the last-found value.
+ static inline bool seen(const char c) {
+ const uint8_t ind = LETTER_BIT(c);
+ if (ind >= COUNT(param)) return false; // Only A-Z
+ const bool b = TEST32(codebits, ind);
+ if (b) {
+ if (param[ind]) {
+ char * const ptr = command_ptr + param[ind];
+ value_ptr = valid_number(ptr) ? ptr : nullptr;
+ }
+ else
+ value_ptr = nullptr;
+ }
+ return b;
+ }
+
+ FORCE_INLINE static constexpr uint32_t letter_bits(const char * const str) {
+ return (str[0] ? _BV32(LETTER_BIT(str[0])) |
+ (str[1] ? _BV32(LETTER_BIT(str[1])) |
+ (str[2] ? _BV32(LETTER_BIT(str[2])) |
+ (str[3] ? _BV32(LETTER_BIT(str[3])) |
+ (str[4] ? _BV32(LETTER_BIT(str[4])) |
+ (str[5] ? _BV32(LETTER_BIT(str[5])) |
+ (str[6] ? _BV32(LETTER_BIT(str[6])) |
+ (str[7] ? _BV32(LETTER_BIT(str[7])) |
+ (str[8] ? _BV32(LETTER_BIT(str[8])) |
+ (str[9] ? _BV32(LETTER_BIT(str[9]))
+ : 0) : 0) : 0) : 0) : 0) : 0) : 0) : 0) : 0) : 0);
+ }
+
+ // At least one of a list of code letters was seen
+ #ifdef CPU_32_BIT
+ FORCE_INLINE static bool seen(const char * const str) { return !!(codebits & letter_bits(str)); }
+ #else
+ FORCE_INLINE static bool seen(const char * const str) {
+ const uint32_t letrbits = letter_bits(str);
+ const uint8_t * const cb = (uint8_t*)&codebits;
+ const uint8_t * const lb = (uint8_t*)&letrbits;
+ return (cb[0] & lb[0]) || (cb[1] & lb[1]) || (cb[2] & lb[2]) || (cb[3] & lb[3]);
+ }
+ #endif
+
+ static inline bool seen_any() { return !!codebits; }
+
+ FORCE_INLINE static bool seen_test(const char c) { return TEST32(codebits, LETTER_BIT(c)); }
+
+ #else // !FASTER_GCODE_PARSER
+
+ #if ENABLED(GCODE_CASE_INSENSITIVE)
+ FORCE_INLINE static char* strgchr(char *p, char g) {
+ auto uppercase = [](char c) {
+ return c + (WITHIN(c, 'a', 'z') ? 'A' - 'a' : 0);
+ };
+ const char d = uppercase(g);
+ for (char cc; (cc = uppercase(*p)); p++) if (cc == d) return p;
+ return nullptr;
+ }
+ #else
+ #define strgchr strchr
+ #endif
+
+ // Code is found in the string. If not found, value_ptr is unchanged.
+ // This allows "if (seen('A')||seen('B'))" to use the last-found value.
+ static inline bool seen(const char c) {
+ char *p = strgchr(command_args, c);
+ const bool b = !!p;
+ if (b) value_ptr = valid_number(&p[1]) ? &p[1] : nullptr;
+ return b;
+ }
+
+ static inline bool seen_any() { return *command_args == '\0'; }
+
+ FORCE_INLINE static bool seen_test(const char c) { return (bool)strgchr(command_args, c); }
+
+ // At least one of a list of code letters was seen
+ static inline bool seen(const char * const str) {
+ for (uint8_t i = 0; const char c = str[i]; i++)
+ if (seen_test(c)) return true;
+ return false;
+ }
+
+ #endif // !FASTER_GCODE_PARSER
+
+ // Seen any axis parameter
+ static inline bool seen_axis() {
+ return seen_test('X') || seen_test('Y') || seen_test('Z') || seen_test('E');
+ }
+
+ #if ENABLED(GCODE_QUOTED_STRINGS)
+ static char* unescape_string(char* &src);
+ #else
+ FORCE_INLINE static char* unescape_string(char* &src) { return src; }
+ #endif
+
+ // Populate all fields by parsing a single line of GCode
+ // This uses 54 bytes of SRAM to speed up seen/value
+ static void parse(char * p);
+
+ #if ENABLED(CNC_COORDINATE_SYSTEMS)
+ // Parse the next parameter as a new command
+ static bool chain();
+ #endif
+
+ // Test whether the parsed command matches the input
+ static inline bool is_command(const char ltr, const uint16_t num) { return command_letter == ltr && codenum == num; }
+
+ // The code value pointer was set
+ FORCE_INLINE static bool has_value() { return !!value_ptr; }
+
+ // Seen a parameter with a value
+ static inline bool seenval(const char c) { return seen(c) && has_value(); }
+
+ // The value as a string
+ static inline char* value_string() { return value_ptr; }
+
+ // Float removes 'E' to prevent scientific notation interpretation
+ static inline float value_float() {
+ if (value_ptr) {
+ char *e = value_ptr;
+ for (;;) {
+ const char c = *e;
+ if (c == '\0' || c == ' ') break;
+ if (c == 'E' || c == 'e') {
+ *e = '\0';
+ const float ret = strtof(value_ptr, nullptr);
+ *e = c;
+ return ret;
+ }
+ ++e;
+ }
+ return strtof(value_ptr, nullptr);
+ }
+ return 0;
+ }
+
+ // Code value as a long or ulong
+ static inline int32_t value_long() { return value_ptr ? strtol(value_ptr, nullptr, 10) : 0L; }
+ static inline uint32_t value_ulong() { return value_ptr ? strtoul(value_ptr, nullptr, 10) : 0UL; }
+
+ // Code value for use as time
+ static inline millis_t value_millis() { return value_ulong(); }
+ static inline millis_t value_millis_from_seconds() { return (millis_t)SEC_TO_MS(value_float()); }
+
+ // Reduce to fewer bits
+ static inline int16_t value_int() { return (int16_t)value_long(); }
+ static inline uint16_t value_ushort() { return (uint16_t)value_long(); }
+ static inline uint8_t value_byte() { return (uint8_t)constrain(value_long(), 0, 255); }
+
+ // Bool is true with no value or non-zero
+ static inline bool value_bool() { return !has_value() || !!value_byte(); }
+
+ // Units modes: Inches, Fahrenheit, Kelvin
+
+ #if ENABLED(INCH_MODE_SUPPORT)
+ static inline float mm_to_linear_unit(const float mm) { return mm / linear_unit_factor; }
+ static inline float mm_to_volumetric_unit(const float mm) { return mm / (volumetric_enabled ? volumetric_unit_factor : linear_unit_factor); }
+
+ // Init linear units by constructor
+ GCodeParser() { set_input_linear_units(LINEARUNIT_MM); }
+
+ static inline void set_input_linear_units(const LinearUnit units) {
+ switch (units) {
+ default:
+ case LINEARUNIT_MM: linear_unit_factor = 1.0f; break;
+ case LINEARUNIT_INCH: linear_unit_factor = 25.4f; break;
+ }
+ volumetric_unit_factor = POW(linear_unit_factor, 3);
+ }
+
+ static inline float axis_unit_factor(const AxisEnum axis) {
+ return (axis >= E_AXIS && volumetric_enabled ? volumetric_unit_factor : linear_unit_factor);
+ }
+
+ static inline float linear_value_to_mm(const float v) { return v * linear_unit_factor; }
+ static inline float axis_value_to_mm(const AxisEnum axis, const float v) { return v * axis_unit_factor(axis); }
+ static inline float per_axis_value(const AxisEnum axis, const float v) { return v / axis_unit_factor(axis); }
+
+ #else
+
+ static inline float mm_to_linear_unit(const float mm) { return mm; }
+ static inline float mm_to_volumetric_unit(const float mm) { return mm; }
+
+ static inline float linear_value_to_mm(const float v) { return v; }
+ static inline float axis_value_to_mm(const AxisEnum, const float v) { return v; }
+ static inline float per_axis_value(const AxisEnum, const float v) { return v; }
+
+ #endif
+
+ static inline bool using_inch_units() { return mm_to_linear_unit(1.0f) != 1.0f; }
+
+ #define IN_TO_MM(I) ((I) * 25.4f)
+ #define MM_TO_IN(M) ((M) / 25.4f)
+ #define LINEAR_UNIT(V) parser.mm_to_linear_unit(V)
+ #define VOLUMETRIC_UNIT(V) parser.mm_to_volumetric_unit(V)
+
+ static inline float value_linear_units() { return linear_value_to_mm(value_float()); }
+ static inline float value_axis_units(const AxisEnum axis) { return axis_value_to_mm(axis, value_float()); }
+ static inline float value_per_axis_units(const AxisEnum axis) { return per_axis_value(axis, value_float()); }
+
+ #if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+
+ static inline void set_input_temp_units(const TempUnit units) { input_temp_units = units; }
+
+ #if HAS_LCD_MENU && DISABLED(DISABLE_M503)
+
+ static inline char temp_units_code() {
+ return input_temp_units == TEMPUNIT_K ? 'K' : input_temp_units == TEMPUNIT_F ? 'F' : 'C';
+ }
+ static inline PGM_P temp_units_name() {
+ return input_temp_units == TEMPUNIT_K ? PSTR("Kelvin") : input_temp_units == TEMPUNIT_F ? PSTR("Fahrenheit") : PSTR("Celsius");
+ }
+ static inline float to_temp_units(const float &f) {
+ switch (input_temp_units) {
+ case TEMPUNIT_F:
+ return f * 0.5555555556f + 32;
+ case TEMPUNIT_K:
+ return f + 273.15f;
+ case TEMPUNIT_C:
+ default:
+ return f;
+ }
+ }
+
+ #endif // HAS_LCD_MENU && !DISABLE_M503
+
+ static inline float value_celsius() {
+ const float f = value_float();
+ switch (input_temp_units) {
+ case TEMPUNIT_F:
+ return (f - 32) * 0.5555555556f;
+ case TEMPUNIT_K:
+ return f - 273.15f;
+ case TEMPUNIT_C:
+ default:
+ return f;
+ }
+ }
+
+ static inline float value_celsius_diff() {
+ switch (input_temp_units) {
+ case TEMPUNIT_F:
+ return value_float() * 0.5555555556f;
+ case TEMPUNIT_C:
+ case TEMPUNIT_K:
+ default:
+ return value_float();
+ }
+ }
+
+ #define TEMP_UNIT(N) parser.to_temp_units(N)
+
+ #else // !TEMPERATURE_UNITS_SUPPORT
+
+ static inline float value_celsius() { return value_float(); }
+ static inline float value_celsius_diff() { return value_float(); }
+
+ #define TEMP_UNIT(N) (N)
+
+ #endif // !TEMPERATURE_UNITS_SUPPORT
+
+ static inline feedRate_t value_feedrate() { return MMM_TO_MMS(value_linear_units()); }
+
+ void unknown_command_warning();
+
+ // Provide simple value accessors with default option
+ static inline char* stringval(const char c, char * const dval=nullptr) { return seenval(c) ? value_string() : dval; }
+ static inline float floatval(const char c, const float dval=0.0) { return seenval(c) ? value_float() : dval; }
+ static inline bool boolval(const char c, const bool dval=false) { return seenval(c) ? value_bool() : (seen(c) ? true : dval); }
+ static inline uint8_t byteval(const char c, const uint8_t dval=0) { return seenval(c) ? value_byte() : dval; }
+ static inline int16_t intval(const char c, const int16_t dval=0) { return seenval(c) ? value_int() : dval; }
+ static inline uint16_t ushortval(const char c, const uint16_t dval=0) { return seenval(c) ? value_ushort() : dval; }
+ static inline int32_t longval(const char c, const int32_t dval=0) { return seenval(c) ? value_long() : dval; }
+ static inline uint32_t ulongval(const char c, const uint32_t dval=0) { return seenval(c) ? value_ulong() : dval; }
+ static inline float linearval(const char c, const float dval=0) { return seenval(c) ? value_linear_units() : dval; }
+ static inline float celsiusval(const char c, const float dval=0) { return seenval(c) ? value_celsius() : dval; }
+
+ #if ENABLED(MARLIN_DEV_MODE)
+
+ static inline uint8_t* hex_adr_val(const char c, uint8_t * const dval=nullptr) {
+ if (!seen(c) || *value_ptr != 'x') return dval;
+ uint8_t *out = nullptr;
+ for (char *vp = value_ptr + 1; HEXCHR(*vp) >= 0; vp++)
+ out = (uint8_t*)((uintptr_t(out) << 8) | HEXCHR(*vp));
+ return out;
+ }
+
+ static inline uint16_t hex_val(const char c, uint16_t const dval=0) {
+ if (!seen(c) || *value_ptr != 'x') return dval;
+ uint16_t out = 0;
+ for (char *vp = value_ptr + 1; HEXCHR(*vp) >= 0; vp++)
+ out = ((out) << 8) | HEXCHR(*vp);
+ return out;
+ }
+
+ #endif
+};
+
+extern GCodeParser parser;
diff --git a/Marlin/src/gcode/probe/G30.cpp b/Marlin/src/gcode/probe/G30.cpp
new file mode 100644
index 0000000..4347f55
--- /dev/null
+++ b/Marlin/src/gcode/probe/G30.cpp
@@ -0,0 +1,66 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_BED_PROBE
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/probe.h"
+#include "../../feature/bedlevel/bedlevel.h"
+
+/**
+ * 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 (default 1)
+ */
+void GcodeSuite::G30() {
+
+ const xy_pos_t pos = { parser.linearval('X', current_position.x + probe.offset_xy.x),
+ parser.linearval('Y', current_position.y + probe.offset_xy.y) };
+
+ if (!probe.can_reach(pos)) return;
+
+ // Disable leveling so the planner won't mess with us
+ TERN_(HAS_LEVELING, set_bed_leveling_enabled(false));
+
+ remember_feedrate_scaling_off();
+
+ const ProbePtRaise raise_after = parser.boolval('E', true) ? PROBE_PT_STOW : PROBE_PT_NONE;
+ const float measured_z = probe.probe_at_point(pos, raise_after, 1);
+ if (!isnan(measured_z))
+ SERIAL_ECHOLNPAIR("Bed X: ", pos.x, " Y: ", pos.y, " Z: ", measured_z);
+
+ restore_feedrate_and_scaling();
+
+ if (raise_after == PROBE_PT_STOW)
+ probe.move_z_after_probing();
+
+ report_current_position();
+}
+
+#endif // HAS_BED_PROBE
diff --git a/Marlin/src/gcode/probe/G31_G32.cpp b/Marlin/src/gcode/probe/G31_G32.cpp
new file mode 100644
index 0000000..af44257
--- /dev/null
+++ b/Marlin/src/gcode/probe/G31_G32.cpp
@@ -0,0 +1,40 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(Z_PROBE_SLED)
+
+#include "../gcode.h"
+#include "../../module/probe.h"
+
+/**
+ * G31: Deploy the Z probe
+ */
+void GcodeSuite::G31() { probe.deploy(); }
+
+/**
+ * G32: Stow the Z probe
+ */
+void GcodeSuite::G32() { probe.stow(); }
+
+#endif // Z_PROBE_SLED
diff --git a/Marlin/src/gcode/probe/G38.cpp b/Marlin/src/gcode/probe/G38.cpp
new file mode 100644
index 0000000..b06cd47
--- /dev/null
+++ b/Marlin/src/gcode/probe/G38.cpp
@@ -0,0 +1,133 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(G38_PROBE_TARGET)
+
+#include "../gcode.h"
+
+#include "../../module/endstops.h"
+#include "../../module/motion.h"
+#include "../../module/stepper.h"
+#include "../../module/probe.h"
+
+inline void G38_single_probe(const uint8_t move_value) {
+ endstops.enable(true);
+ G38_move = move_value;
+ prepare_line_to_destination();
+ planner.synchronize();
+ G38_move = 0;
+ endstops.hit_on_purpose();
+ set_current_from_steppers_for_axis(ALL_AXES);
+ sync_plan_position();
+}
+
+inline bool G38_run_probe() {
+
+ bool G38_pass_fail = false;
+
+ #if MULTIPLE_PROBING > 1
+ // Get direction of move and retract
+ xyz_float_t retract_mm;
+ LOOP_XYZ(i) {
+ const float dist = destination[i] - current_position[i];
+ retract_mm[i] = ABS(dist) < G38_MINIMUM_MOVE ? 0 : home_bump_mm((AxisEnum)i) * (dist > 0 ? -1 : 1);
+ }
+ #endif
+
+ planner.synchronize(); // wait until the machine is idle
+
+ // Move flag value
+ #if ENABLED(G38_PROBE_AWAY)
+ const uint8_t move_value = parser.subcode;
+ #else
+ constexpr uint8_t move_value = 1;
+ #endif
+
+ G38_did_trigger = false;
+
+ // Move until destination reached or target hit
+ G38_single_probe(move_value);
+
+ if (G38_did_trigger) {
+
+ G38_pass_fail = true;
+
+ #if MULTIPLE_PROBING > 1
+ // Move away by the retract distance
+ destination = current_position + retract_mm;
+ endstops.enable(false);
+ prepare_line_to_destination();
+ planner.synchronize();
+
+ REMEMBER(fr, feedrate_mm_s, feedrate_mm_s * 0.25);
+
+ // Bump the target more slowly
+ destination -= retract_mm * 2;
+
+ G38_single_probe(move_value);
+ #endif
+ }
+
+ endstops.not_homing();
+ return G38_pass_fail;
+}
+
+/**
+ * G38 Probe Target
+ *
+ * G38.2 - Probe toward workpiece, stop on contact, signal error if failure
+ * G38.3 - Probe toward workpiece, stop on contact
+ *
+ * With G38_PROBE_AWAY:
+ *
+ * G38.4 - Probe away from workpiece, stop on contact break, signal error if failure
+ * G38.5 - Probe away from workpiece, stop on contact break
+ */
+void GcodeSuite::G38(const int8_t subcode) {
+ // Get X Y Z E F
+ get_destination_from_command();
+
+ remember_feedrate_scaling_off();
+
+ const bool error_on_fail =
+ #if ENABLED(G38_PROBE_AWAY)
+ !TEST(subcode, 0)
+ #else
+ (subcode == 2)
+ #endif
+ ;
+
+ // If any axis has enough movement, do the move
+ LOOP_XYZ(i)
+ if (ABS(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() && error_on_fail) SERIAL_ERROR_MSG("Failed to reach target");
+ break;
+ }
+
+ restore_feedrate_and_scaling();
+}
+
+#endif // G38_PROBE_TARGET
diff --git a/Marlin/src/gcode/probe/M401_M402.cpp b/Marlin/src/gcode/probe/M401_M402.cpp
new file mode 100644
index 0000000..bd9bb44
--- /dev/null
+++ b/Marlin/src/gcode/probe/M401_M402.cpp
@@ -0,0 +1,49 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_BED_PROBE
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/probe.h"
+
+/**
+ * M401: Deploy and activate the Z probe
+ */
+void GcodeSuite::M401() {
+ probe.deploy();
+ TERN_(PROBE_TARE, probe.tare());
+ report_current_position();
+}
+
+/**
+ * M402: Deactivate and stow the Z probe
+ */
+void GcodeSuite::M402() {
+ probe.stow();
+ probe.move_z_after_probing();
+ report_current_position();
+}
+
+#endif // HAS_BED_PROBE
diff --git a/Marlin/src/gcode/probe/M851.cpp b/Marlin/src/gcode/probe/M851.cpp
new file mode 100644
index 0000000..04b293d
--- /dev/null
+++ b/Marlin/src/gcode/probe/M851.cpp
@@ -0,0 +1,97 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_BED_PROBE
+
+#include "../gcode.h"
+#include "../../feature/bedlevel/bedlevel.h"
+#include "../../module/probe.h"
+
+/**
+ * M851: Set the nozzle-to-probe offsets in current units
+ */
+void GcodeSuite::M851() {
+
+ // Show usage with no parameters
+ if (!parser.seen("XYZ")) {
+ SERIAL_ECHOLNPAIR_P(
+ #if HAS_PROBE_XY_OFFSET
+ PSTR(STR_PROBE_OFFSET " X"), probe.offset_xy.x, SP_Y_STR, probe.offset_xy.y, SP_Z_STR
+ #else
+ PSTR(STR_PROBE_OFFSET " X0 Y0 Z")
+ #endif
+ , probe.offset.z
+ );
+ return;
+ }
+
+ // Start with current offsets and modify
+ xyz_pos_t offs = probe.offset;
+
+ // Assume no errors
+ bool ok = true;
+
+ if (parser.seenval('X')) {
+ const float x = parser.value_float();
+ #if HAS_PROBE_XY_OFFSET
+ if (WITHIN(x, -(X_BED_SIZE), X_BED_SIZE))
+ offs.x = x;
+ else {
+ SERIAL_ECHOLNPAIR("?X out of range (-", int(X_BED_SIZE), " to ", int(X_BED_SIZE), ")");
+ ok = false;
+ }
+ #else
+ if (x) SERIAL_ECHOLNPAIR("?X must be 0 (NOZZLE_AS_PROBE)."); // ...but let 'ok' stay true
+ #endif
+ }
+
+ if (parser.seenval('Y')) {
+ const float y = parser.value_float();
+ #if HAS_PROBE_XY_OFFSET
+ if (WITHIN(y, -(Y_BED_SIZE), Y_BED_SIZE))
+ offs.y = y;
+ else {
+ SERIAL_ECHOLNPAIR("?Y out of range (-", int(Y_BED_SIZE), " to ", int(Y_BED_SIZE), ")");
+ ok = false;
+ }
+ #else
+ if (y) SERIAL_ECHOLNPAIR("?Y must be 0 (NOZZLE_AS_PROBE)."); // ...but let 'ok' stay true
+ #endif
+ }
+
+ if (parser.seenval('Z')) {
+ const float z = parser.value_float();
+ if (WITHIN(z, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX))
+ offs.z = z;
+ else {
+ SERIAL_ECHOLNPAIR("?Z out of range (", int(Z_PROBE_OFFSET_RANGE_MIN), " to ", int(Z_PROBE_OFFSET_RANGE_MAX), ")");
+ ok = false;
+ }
+ }
+
+ // Save the new offsets
+ if (ok) probe.offset = offs;
+}
+
+#endif // HAS_BED_PROBE
diff --git a/Marlin/src/gcode/probe/M951.cpp b/Marlin/src/gcode/probe/M951.cpp
new file mode 100644
index 0000000..f461fc2
--- /dev/null
+++ b/Marlin/src/gcode/probe/M951.cpp
@@ -0,0 +1,71 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if ENABLED(MAGNETIC_PARKING_EXTRUDER)
+
+#include "../gcode.h"
+#include "../../module/tool_change.h"
+#include "../../module/motion.h"
+
+mpe_settings_t mpe_settings;
+
+inline void mpe_settings_report() {
+ SERIAL_ECHO_MSG("Magnetic Parking Extruder");
+ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("L: Left parking :", mpe_settings.parking_xpos[0]);
+ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("R: Right parking :", mpe_settings.parking_xpos[1]);
+ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("I: Grab Offset :", mpe_settings.grab_distance);
+ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("J: Normal speed :", long(MMS_TO_MMM(mpe_settings.slow_feedrate)));
+ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("H: High speed :", long(MMS_TO_MMM(mpe_settings.fast_feedrate)));
+ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("D: Distance trav.:", mpe_settings.travel_distance);
+ SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("C: Compenstion :", mpe_settings.compensation_factor);
+}
+
+void mpe_settings_init() {
+ constexpr float pex[2] = PARKING_EXTRUDER_PARKING_X;
+ mpe_settings.parking_xpos[0] = pex[0]; // M951 L
+ mpe_settings.parking_xpos[1] = pex[1]; // M951 R
+ mpe_settings.grab_distance = PARKING_EXTRUDER_GRAB_DISTANCE; // M951 I
+ TERN_(HAS_HOME_OFFSET, set_home_offset(X_AXIS, mpe_settings.grab_distance * -1));
+ mpe_settings.slow_feedrate = MMM_TO_MMS(MPE_SLOW_SPEED); // M951 J
+ mpe_settings.fast_feedrate = MMM_TO_MMS(MPE_FAST_SPEED); // M951 H
+ mpe_settings.travel_distance = MPE_TRAVEL_DISTANCE; // M951 D
+ mpe_settings.compensation_factor = MPE_COMPENSATION; // M951 C
+ mpe_settings_report();
+}
+
+void GcodeSuite::M951() {
+ if (parser.seenval('L')) mpe_settings.parking_xpos[0] = parser.value_linear_units();
+ if (parser.seenval('R')) mpe_settings.parking_xpos[1] = parser.value_linear_units();
+ if (parser.seenval('I')) {
+ mpe_settings.grab_distance = parser.value_linear_units();
+ TERN_(HAS_HOME_OFFSET, set_home_offset(X_AXIS, mpe_settings.grab_distance * -1));
+ }
+ if (parser.seenval('J')) mpe_settings.slow_feedrate = MMM_TO_MMS(parser.value_linear_units());
+ if (parser.seenval('H')) mpe_settings.fast_feedrate = MMM_TO_MMS(parser.value_linear_units());
+ if (parser.seenval('D')) mpe_settings.travel_distance = parser.value_linear_units();
+ if (parser.seenval('C')) mpe_settings.compensation_factor = parser.value_float();
+ if (!parser.seen("CDHIJLR")) mpe_settings_report();
+}
+
+#endif // MAGNETIC_PARKING_EXTRUDER
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
new file mode 100644
index 0000000..4c42f7e
--- /dev/null
+++ b/Marlin/src/gcode/queue.cpp
@@ -0,0 +1,699 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * queue.cpp - The G-code command queue
+ */
+
+#include "queue.h"
+GCodeQueue queue;
+
+#include "gcode.h"
+
+#include "../lcd/marlinui.h"
+#include "../sd/cardreader.h"
+#include "../module/motion.h"
+#include "../module/planner.h"
+#include "../module/temperature.h"
+#include "../MarlinCore.h"
+
+#if ENABLED(PRINTER_EVENT_LEDS)
+ #include "../feature/leds/printer_event_leds.h"
+#endif
+
+#if HAS_ETHERNET
+ #include "../feature/ethernet.h"
+#endif
+
+#if ENABLED(BINARY_FILE_TRANSFER)
+ #include "../feature/binary_stream.h"
+#endif
+
+#if ENABLED(MEATPACK)
+ #include "../feature/meatpack.h"
+#endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ #include "../feature/powerloss.h"
+#endif
+
+#if ENABLED(GCODE_REPEAT_MARKERS)
+ #include "../feature/repeat.h"
+#endif
+
+// Frequently used G-code strings
+PGMSTR(G28_STR, "G28");
+
+/**
+ * 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<int> sets the current line number.
+ */
+long GCodeQueue::last_N[NUM_SERIAL];
+
+/**
+ * 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 gcode.process_next_command method parses the next
+ * command and hands off execution to individual handler functions.
+ */
+uint8_t GCodeQueue::length = 0, // Count of commands in the queue
+ GCodeQueue::index_r = 0, // Ring buffer read position
+ GCodeQueue::index_w = 0; // Ring buffer write position
+
+char GCodeQueue::command_buffer[BUFSIZE][MAX_CMD_SIZE];
+
+/*
+ * The port that the command was received on
+ */
+#if HAS_MULTI_SERIAL
+ serial_index_t GCodeQueue::port[BUFSIZE];
+#endif
+
+/**
+ * Serial command injection
+ */
+
+// Number of characters read in the current line of serial input
+static int serial_count[NUM_SERIAL] = { 0 };
+
+bool send_ok[BUFSIZE];
+
+/**
+ * Next Injected PROGMEM Command pointer. (nullptr == empty)
+ * Internal commands are enqueued ahead of serial / SD commands.
+ */
+PGM_P GCodeQueue::injected_commands_P; // = nullptr
+
+/**
+ * Injected SRAM Commands
+ */
+char GCodeQueue::injected_commands[64]; // = { 0 }
+
+GCodeQueue::GCodeQueue() {
+ // Send "ok" after commands by default
+ LOOP_L_N(i, COUNT(send_ok)) send_ok[i] = true;
+}
+
+/**
+ * Check whether there are any commands yet to be executed
+ */
+bool GCodeQueue::has_commands_queued() {
+ return queue.length || injected_commands_P || injected_commands[0];
+}
+
+/**
+ * Clear the Marlin command queue
+ */
+void GCodeQueue::clear() {
+ index_r = index_w = length = 0;
+}
+
+/**
+ * Once a new command is in the ring buffer, call this to commit it
+ */
+void GCodeQueue::_commit_command(bool say_ok
+ #if HAS_MULTI_SERIAL
+ , serial_index_t serial_ind/*=-1*/
+ #endif
+) {
+ send_ok[index_w] = say_ok;
+ TERN_(HAS_MULTI_SERIAL, port[index_w] = serial_ind);
+ TERN_(POWER_LOSS_RECOVERY, recovery.commit_sdpos(index_w));
+ if (++index_w >= BUFSIZE) index_w = 0;
+ length++;
+}
+
+/**
+ * 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.
+ */
+bool GCodeQueue::_enqueue(const char* cmd, bool say_ok/*=false*/
+ #if HAS_MULTI_SERIAL
+ , serial_index_t serial_ind/*=-1*/
+ #endif
+) {
+ if (*cmd == ';' || length >= BUFSIZE) return false;
+ strcpy(command_buffer[index_w], cmd);
+ _commit_command(say_ok
+ #if HAS_MULTI_SERIAL
+ , serial_ind
+ #endif
+ );
+ return true;
+}
+
+/**
+ * Enqueue with Serial Echo
+ * Return true if the command was consumed
+ */
+bool GCodeQueue::enqueue_one(const char* cmd) {
+
+ //SERIAL_ECHOPGM("enqueue_one(\"");
+ //SERIAL_ECHO(cmd);
+ //SERIAL_ECHOPGM("\") \n");
+
+ if (*cmd == 0 || ISEOL(*cmd)) return true;
+
+ if (_enqueue(cmd)) {
+ SERIAL_ECHO_MSG(STR_ENQUEUEING, cmd, "\"");
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Process the next "immediate" command from PROGMEM.
+ * Return 'true' if any commands were processed.
+ */
+bool GCodeQueue::process_injected_command_P() {
+ if (!injected_commands_P) return false;
+
+ char c;
+ size_t i = 0;
+ while ((c = pgm_read_byte(&injected_commands_P[i])) && c != '\n') i++;
+
+ // Extract current command and move pointer to next command
+ char cmd[i + 1];
+ memcpy_P(cmd, injected_commands_P, i);
+ cmd[i] = '\0';
+ injected_commands_P = c ? injected_commands_P + i + 1 : nullptr;
+
+ // Execute command if non-blank
+ if (i) {
+ parser.parse(cmd);
+ gcode.process_parsed_command();
+ }
+ return true;
+}
+
+/**
+ * Process the next "immediate" command from SRAM.
+ * Return 'true' if any commands were processed.
+ */
+bool GCodeQueue::process_injected_command() {
+ if (injected_commands[0] == '\0') return false;
+
+ char c;
+ size_t i = 0;
+ while ((c = injected_commands[i]) && c != '\n') i++;
+
+ // Execute a non-blank command
+ if (i) {
+ injected_commands[i] = '\0';
+ parser.parse(injected_commands);
+ gcode.process_parsed_command();
+ }
+
+ // Copy the next command into place
+ for (
+ uint8_t d = 0, s = i + !!c; // dst, src
+ (injected_commands[d] = injected_commands[s]); // copy, exit if 0
+ d++, s++ // next dst, src
+ );
+
+ return true;
+}
+
+/**
+ * Enqueue and return only when commands are actually enqueued.
+ * Never call this from a G-code handler!
+ */
+void GCodeQueue::enqueue_one_now(const char* cmd) { while (!enqueue_one(cmd)) idle(); }
+
+/**
+ * Attempt to enqueue a single G-code command
+ * and return 'true' if successful.
+ */
+bool GCodeQueue::enqueue_one_P(PGM_P const pgcode) {
+ size_t i = 0;
+ PGM_P p = pgcode;
+ char c;
+ while ((c = pgm_read_byte(&p[i])) && c != '\n') i++;
+ char cmd[i + 1];
+ memcpy_P(cmd, p, i);
+ cmd[i] = '\0';
+ return _enqueue(cmd);
+}
+
+/**
+ * Enqueue from program memory and return only when commands are actually enqueued
+ * Never call this from a G-code handler!
+ */
+void GCodeQueue::enqueue_now_P(PGM_P const pgcode) {
+ size_t i = 0;
+ PGM_P p = pgcode;
+ for (;;) {
+ char c;
+ while ((c = pgm_read_byte(&p[i])) && c != '\n') i++;
+ char cmd[i + 1];
+ memcpy_P(cmd, p, i);
+ cmd[i] = '\0';
+ enqueue_one_now(cmd);
+ if (!c) break;
+ p += i + 1;
+ }
+}
+
+/**
+ * Send an "ok" message to the host, indicating
+ * that a command was successfully processed.
+ *
+ * If ADVANCED_OK is enabled also include:
+ * N<int> Line number of the command, if any
+ * P<int> Planner space remaining
+ * B<int> Block queue space remaining
+ */
+void GCodeQueue::ok_to_send() {
+ #if HAS_MULTI_SERIAL
+ const serial_index_t serial_ind = command_port();
+ if (serial_ind < 0) return;
+ PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
+ #endif
+ if (!send_ok[index_r]) return;
+ SERIAL_ECHOPGM(STR_OK);
+ #if ENABLED(ADVANCED_OK)
+ char* p = command_buffer[index_r];
+ if (*p == 'N') {
+ SERIAL_ECHO(' ');
+ SERIAL_ECHO(*p++);
+ while (NUMERIC_SIGNED(*p))
+ SERIAL_ECHO(*p++);
+ }
+ SERIAL_ECHOPAIR_P(SP_P_STR, int(planner.moves_free()),
+ SP_B_STR, int(BUFSIZE - length));
+ #endif
+ SERIAL_EOL();
+}
+
+/**
+ * Send a "Resend: nnn" message to the host to
+ * indicate that a command needs to be re-sent.
+ */
+void GCodeQueue::flush_and_request_resend() {
+ const serial_index_t serial_ind = command_port();
+ #if HAS_MULTI_SERIAL
+ if (serial_ind < 0) return; // Never mind. Command came from SD or Flash Drive
+ PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
+ #endif
+ SERIAL_FLUSH();
+ SERIAL_ECHOPGM(STR_RESEND);
+ SERIAL_ECHOLN(last_N[serial_ind] + 1);
+ ok_to_send();
+}
+
+inline bool serial_data_available() {
+ byte data_available = 0;
+ if (MYSERIAL0.available()) data_available++;
+ #ifdef SERIAL_PORT_2
+ const bool port2_open = TERN1(HAS_ETHERNET, ethernet.have_telnet_client);
+ if (port2_open && MYSERIAL1.available()) data_available++;
+ #endif
+ return data_available > 0;
+}
+
+inline int read_serial(const uint8_t index) {
+ switch (index) {
+ case 0: return MYSERIAL0.read();
+ case 1: {
+ #if HAS_MULTI_SERIAL
+ const bool port2_open = TERN1(HAS_ETHERNET, ethernet.have_telnet_client);
+ if (port2_open) return MYSERIAL1.read();
+ #endif
+ }
+ default: return -1;
+ }
+}
+
+void GCodeQueue::gcode_line_error(PGM_P const err, const serial_index_t serial_ind) {
+ PORT_REDIRECT(SERIAL_PORTMASK(serial_ind)); // Reply to the serial port that sent the command
+ SERIAL_ERROR_START();
+ serialprintPGM(err);
+ SERIAL_ECHOLN(last_N[serial_ind]);
+ while (read_serial(serial_ind) != -1); // Clear out the RX buffer
+ flush_and_request_resend();
+ serial_count[serial_ind] = 0;
+}
+
+FORCE_INLINE bool is_M29(const char * const cmd) { // matches "M29" & "M29 ", but not "M290", etc
+ const char * const m29 = strstr_P(cmd, PSTR("M29"));
+ return m29 && !NUMERIC(m29[3]);
+}
+
+#define PS_NORMAL 0
+#define PS_EOL 1
+#define PS_QUOTED 2
+#define PS_PAREN 3
+#define PS_ESC 4
+
+inline void process_stream_char(const char c, uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) {
+
+ if (sis == PS_EOL) return; // EOL comment or overflow
+
+ #if ENABLED(PAREN_COMMENTS)
+ else if (sis == PS_PAREN) { // Inline comment
+ if (c == ')') sis = PS_NORMAL;
+ return;
+ }
+ #endif
+
+ else if (sis >= PS_ESC) // End escaped char
+ sis -= PS_ESC;
+
+ else if (c == '\\') { // Start escaped char
+ sis += PS_ESC;
+ if (sis == PS_ESC) return; // Keep if quoting
+ }
+
+ #if ENABLED(GCODE_QUOTED_STRINGS)
+
+ else if (sis == PS_QUOTED) {
+ if (c == '"') sis = PS_NORMAL; // End quoted string
+ }
+ else if (c == '"') // Start quoted string
+ sis = PS_QUOTED;
+
+ #endif
+
+ else if (c == ';') { // Start end-of-line comment
+ sis = PS_EOL;
+ return;
+ }
+
+ #if ENABLED(PAREN_COMMENTS)
+ else if (c == '(') { // Start inline comment
+ sis = PS_PAREN;
+ return;
+ }
+ #endif
+
+ // Backspace erases previous characters
+ if (c == 0x08) {
+ if (ind) buff[--ind] = '\0';
+ }
+ else {
+ buff[ind++] = c;
+ if (ind >= MAX_CMD_SIZE - 1)
+ sis = PS_EOL; // Skip the rest on overflow
+ }
+}
+
+/**
+ * Handle a line being completed. For an empty line
+ * keep sensor readings going and watchdog alive.
+ */
+inline bool process_line_done(uint8_t &sis, char (&buff)[MAX_CMD_SIZE], int &ind) {
+ sis = PS_NORMAL; // "Normal" Serial Input State
+ buff[ind] = '\0'; // Of course, I'm a Terminator.
+ const bool is_empty = (ind == 0); // An empty line?
+ if (is_empty)
+ thermalManager.manage_heater(); // Keep sensors satisfied
+ else
+ ind = 0; // Start a new line
+ return is_empty; // Inform the caller
+}
+
+/**
+ * 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.
+ */
+void GCodeQueue::get_serial_commands() {
+ static char serial_line_buffer[NUM_SERIAL][MAX_CMD_SIZE];
+
+ static uint8_t serial_input_state[NUM_SERIAL] = { PS_NORMAL };
+
+ #if ENABLED(BINARY_FILE_TRANSFER)
+ if (card.flag.binary_mode) {
+ /**
+ * For binary stream file transfer, use serial_line_buffer as the working
+ * receive buffer (which limits the packet size to MAX_CMD_SIZE).
+ * The receive buffer also limits the packet size for reliable transmission.
+ */
+ binaryStream[card.transfer_port_index].receive(serial_line_buffer[card.transfer_port_index]);
+ return;
+ }
+ #endif
+
+ // 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 (length == 0 && !serial_data_available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) {
+ SERIAL_ECHOLNPGM(STR_WAIT);
+ last_command_time = ms;
+ }
+ #endif
+
+ /**
+ * Loop while serial characters are incoming and the queue is not full
+ */
+ while (length < BUFSIZE && serial_data_available()) {
+ LOOP_L_N(p, NUM_SERIAL) {
+
+ const int c = read_serial(p);
+ if (c < 0) continue;
+
+ #if ENABLED(MEATPACK)
+ meatpack.handle_rx_char(uint8_t(c), p);
+ char c_res[2] = { 0, 0 };
+ const uint8_t char_count = meatpack.get_result_char(c_res);
+ #else
+ constexpr uint8_t char_count = 1;
+ #endif
+
+ LOOP_L_N(char_index, char_count) {
+ const char serial_char = TERN(MEATPACK, c_res[char_index], c);
+
+ if (ISEOL(serial_char)) {
+
+ // Reset our state, continue if the line was empty
+ if (process_line_done(serial_input_state[p], serial_line_buffer[p], serial_count[p]))
+ continue;
+
+ char* command = serial_line_buffer[p];
+
+ while (*command == ' ') command++; // Skip leading spaces
+ char *npos = (*command == 'N') ? command : nullptr; // Require the N parameter to start the line
+
+ if (npos) {
+
+ const bool M110 = !!strstr_P(command, PSTR("M110"));
+
+ if (M110) {
+ char* n2pos = strchr(command + 4, 'N');
+ if (n2pos) npos = n2pos;
+ }
+
+ const long gcode_N = strtol(npos + 1, nullptr, 10);
+
+ if (gcode_N != last_N[p] + 1 && !M110)
+ return gcode_line_error(PSTR(STR_ERR_LINE_NO), p);
+
+ char *apos = strrchr(command, '*');
+ if (apos) {
+ uint8_t checksum = 0, count = uint8_t(apos - command);
+ while (count) checksum ^= command[--count];
+ if (strtol(apos + 1, nullptr, 10) != checksum)
+ return gcode_line_error(PSTR(STR_ERR_CHECKSUM_MISMATCH), p);
+ }
+ else
+ return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p);
+
+ last_N[p] = gcode_N;
+ }
+ #if ENABLED(SDSUPPORT)
+ // Pronterface "M29" and "M29 " has no line number
+ else if (card.flag.saving && !is_M29(command))
+ return gcode_line_error(PSTR(STR_ERR_NO_CHECKSUM), p);
+ #endif
+
+ //
+ // Movement commands give an alert when the machine is stopped
+ //
+
+ if (IsStopped()) {
+ char* gpos = strchr(command, 'G');
+ if (gpos) {
+ switch (strtol(gpos + 1, nullptr, 10)) {
+ case 0: case 1:
+ #if ENABLED(ARC_SUPPORT)
+ case 2: case 3:
+ #endif
+ #if ENABLED(BEZIER_CURVE_SUPPORT)
+ case 5:
+ #endif
+ PORT_REDIRECT(SERIAL_PORTMASK(p)); // Reply to the serial port that sent the command
+ SERIAL_ECHOLNPGM(STR_ERR_STOPPED);
+ LCD_MESSAGEPGM(MSG_STOPPED);
+ break;
+ }
+ }
+ }
+
+ #if DISABLED(EMERGENCY_PARSER)
+ // Process critical commands early
+ if (command[0] == 'M') switch (command[3]) {
+ case '8': if (command[2] == '0' && command[1] == '1') { wait_for_heatup = false; TERN_(HAS_LCD_MENU, wait_for_user = false); } break;
+ case '2': if (command[2] == '1' && command[1] == '1') kill(M112_KILL_STR, nullptr, true); break;
+ case '0': if (command[1] == '4' && command[2] == '1') quickstop_stepper(); break;
+ }
+ #endif
+
+ #if defined(NO_TIMEOUTS) && NO_TIMEOUTS > 0
+ last_command_time = ms;
+ #endif
+
+ // Add the command to the queue
+ _enqueue(serial_line_buffer[p], true
+ #if HAS_MULTI_SERIAL
+ , p
+ #endif
+ );
+ }
+ else
+ process_stream_char(serial_char, serial_input_state[p], serial_line_buffer[p], serial_count[p]);
+
+ } // char_count loop
+
+ } // NUM_SERIAL loop
+ } // queue has space, serial has data
+}
+
+#if ENABLED(SDSUPPORT)
+
+ /**
+ * Get lines from the SD Card until the command buffer is full
+ * or until the end of the file is reached. Because this method
+ * always receives complete command-lines, they can go directly
+ * into the main command queue.
+ */
+ inline void GCodeQueue::get_sdcard_commands() {
+ static uint8_t sd_input_state = PS_NORMAL;
+
+ if (!IS_SD_PRINTING()) return;
+
+ int sd_count = 0;
+ while (length < BUFSIZE && !card.eof()) {
+ const int16_t n = card.get();
+ const bool card_eof = card.eof();
+ if (n < 0 && !card_eof) { SERIAL_ERROR_MSG(STR_SD_ERR_READ); continue; }
+
+ const char sd_char = (char)n;
+ const bool is_eol = ISEOL(sd_char);
+ if (is_eol || card_eof) {
+
+ // Reset stream state, terminate the buffer, and commit a non-empty command
+ if (!is_eol && sd_count) ++sd_count; // End of file with no newline
+ if (!process_line_done(sd_input_state, command_buffer[index_w], sd_count)) {
+
+ // M808 S saves the sdpos of the next line. M808 loops to a new sdpos.
+ TERN_(GCODE_REPEAT_MARKERS, repeat.early_parse_M808(command_buffer[index_w]));
+
+ // Put the new command into the buffer (no "ok" sent)
+ _commit_command(false);
+
+ // Prime Power-Loss Recovery for the NEXT _commit_command
+ TERN_(POWER_LOSS_RECOVERY, recovery.cmd_sdpos = card.getIndex());
+ }
+
+ if (card.eof()) card.fileHasFinished(); // Handle end of file reached
+ }
+ else
+ process_stream_char(sd_char, sd_input_state, command_buffer[index_w], sd_count);
+ }
+ }
+
+#endif // SDSUPPORT
+
+/**
+ * Add to the circular command queue the next command from:
+ * - The command-injection queues (injected_commands_P, injected_commands)
+ * - The active serial input (usually USB)
+ * - The SD card file being actively printed
+ */
+void GCodeQueue::get_available_commands() {
+
+ get_serial_commands();
+
+ TERN_(SDSUPPORT, get_sdcard_commands());
+}
+
+/**
+ * Get the next command in the queue, optionally log it to SD, then dispatch it
+ */
+void GCodeQueue::advance() {
+
+ // Process immediate commands
+ if (process_injected_command_P() || process_injected_command()) return;
+
+ // Return if the G-code buffer is empty
+ if (!length) return;
+
+ #if ENABLED(SDSUPPORT)
+
+ if (card.flag.saving) {
+ char* command = command_buffer[index_r];
+ if (is_M29(command)) {
+ // M29 closes the file
+ card.closefile();
+ SERIAL_ECHOLNPGM(STR_FILE_SAVED);
+
+ #if !defined(__AVR__) || !defined(USBCON)
+ #if ENABLED(SERIAL_STATS_DROPPED_RX)
+ SERIAL_ECHOLNPAIR("Dropped bytes: ", MYSERIAL0.dropped());
+ #endif
+ #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED)
+ SERIAL_ECHOLNPAIR("Max RX Queue Size: ", MYSERIAL0.rxMaxEnqueued());
+ #endif
+ #endif
+
+ ok_to_send();
+ }
+ else {
+ // Write the string from the read buffer to SD
+ card.write_command(command);
+ if (card.flag.logging)
+ gcode.process_next_command(); // The card is saving because it's logging
+ else
+ ok_to_send();
+ }
+ }
+ else
+ gcode.process_next_command();
+
+ #else
+
+ gcode.process_next_command();
+
+ #endif // SDSUPPORT
+
+ // The queue may be reset by a command handler or by code invoked by idle() within a handler
+ --length;
+ if (++index_r >= BUFSIZE) index_r = 0;
+
+}
diff --git a/Marlin/src/gcode/queue.h b/Marlin/src/gcode/queue.h
new file mode 100644
index 0000000..d677146
--- /dev/null
+++ b/Marlin/src/gcode/queue.h
@@ -0,0 +1,187 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+/**
+ * queue.h - The G-code command queue, which holds commands before they
+ * go to the parser and dispatcher.
+ */
+
+#include "../inc/MarlinConfig.h"
+
+class GCodeQueue {
+public:
+ /**
+ * GCode line number handling. Hosts may include line numbers when sending
+ * commands to Marlin, and lines will be checked for sequentiality.
+ * M110 N<int> sets the current line number.
+ */
+
+ static long last_N[NUM_SERIAL];
+
+ /**
+ * 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 gcode.process_next_command method parses the next
+ * command and hands off execution to individual handler functions.
+ */
+ static uint8_t length, // Count of commands in the queue
+ index_r; // Ring buffer read position
+
+ static char command_buffer[BUFSIZE][MAX_CMD_SIZE];
+
+ /**
+ * The port that the command was received on
+ */
+ #if HAS_MULTI_SERIAL
+ static serial_index_t port[BUFSIZE];
+ #endif
+ static inline serial_index_t command_port() { return TERN0(HAS_MULTI_SERIAL, port[index_r]); }
+
+ GCodeQueue();
+
+ /**
+ * Clear the Marlin command queue
+ */
+ static void clear();
+
+ /**
+ * Next Injected Command (PROGMEM) pointer. (nullptr == empty)
+ * Internal commands are enqueued ahead of serial / SD commands.
+ */
+ static PGM_P injected_commands_P;
+
+ /**
+ * Injected Commands (SRAM)
+ */
+ static char injected_commands[64];
+
+ /**
+ * Enqueue command(s) to run from PROGMEM. Drained by process_injected_command_P().
+ * Don't inject comments or use leading spaces!
+ * Aborts the current PROGMEM queue so only use for one or two commands.
+ */
+ static inline void inject_P(PGM_P const pgcode) { injected_commands_P = pgcode; }
+
+ /**
+ * Enqueue command(s) to run from SRAM. Drained by process_injected_command().
+ * Aborts the current SRAM queue so only use for one or two commands.
+ */
+ static inline void inject(char * const gcode) {
+ strncpy(injected_commands, gcode, sizeof(injected_commands) - 1);
+ }
+
+ /**
+ * Enqueue and return only when commands are actually enqueued
+ */
+ static void enqueue_one_now(const char* cmd);
+
+ /**
+ * Attempt to enqueue a single G-code command
+ * and return 'true' if successful.
+ */
+ static bool enqueue_one_P(PGM_P const pgcode);
+
+ /**
+ * Enqueue from program memory and return only when commands are actually enqueued
+ */
+ static void enqueue_now_P(PGM_P const cmd);
+
+ /**
+ * Check whether there are any commands yet to be executed
+ */
+ static bool has_commands_queued();
+
+ /**
+ * Get the next command in the queue, optionally log it to SD, then dispatch it
+ */
+ static void advance();
+
+ /**
+ * 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
+ */
+ static void get_available_commands();
+
+ /**
+ * Send an "ok" message to the host, indicating
+ * that a command was successfully processed.
+ *
+ * If ADVANCED_OK is enabled also include:
+ * N<int> Line number of the command, if any
+ * P<int> Planner space remaining
+ * B<int> Block queue space remaining
+ */
+ static void ok_to_send();
+
+ /**
+ * Clear the serial line and request a resend of
+ * the next expected line number.
+ */
+ static void flush_and_request_resend();
+
+private:
+
+ static uint8_t index_w; // Ring buffer write position
+
+ static void get_serial_commands();
+
+ #if ENABLED(SDSUPPORT)
+ static void get_sdcard_commands();
+ #endif
+
+ static void _commit_command(bool say_ok
+ #if HAS_MULTI_SERIAL
+ , serial_index_t serial_ind=-1
+ #endif
+ );
+
+ static bool _enqueue(const char* cmd, bool say_ok=false
+ #if HAS_MULTI_SERIAL
+ , serial_index_t serial_ind=-1
+ #endif
+ );
+
+ // Process the next "immediate" command (PROGMEM)
+ static bool process_injected_command_P();
+
+ // Process the next "immediate" command (SRAM)
+ static bool process_injected_command();
+
+ /**
+ * Enqueue with Serial Echo
+ * Return true on success
+ */
+ static bool enqueue_one(const char* cmd);
+
+ static void gcode_line_error(PGM_P const err, const serial_index_t serial_ind);
+
+};
+
+extern GCodeQueue queue;
+
+extern const char G28_STR[];
diff --git a/Marlin/src/gcode/scara/M360-M364.cpp b/Marlin/src/gcode/scara/M360-M364.cpp
new file mode 100644
index 0000000..562beee
--- /dev/null
+++ b/Marlin/src/gcode/scara/M360-M364.cpp
@@ -0,0 +1,81 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(MORGAN_SCARA)
+
+#include "../gcode.h"
+#include "../../module/scara.h"
+#include "../../module/motion.h"
+#include "../../MarlinCore.h" // for IsRunning()
+
+inline bool SCARA_move_to_cal(const uint8_t delta_a, const uint8_t delta_b) {
+ if (IsRunning()) {
+ forward_kinematics_SCARA(delta_a, delta_b);
+ do_blocking_move_to_xy(cartes);
+ return true;
+ }
+ return false;
+}
+
+/**
+ * M360: SCARA calibration: Move to cal-position ThetaA (0 deg calibration)
+ */
+bool GcodeSuite::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)
+ */
+bool GcodeSuite::M361() {
+ SERIAL_ECHOLNPGM(" Cal: Theta 90");
+ return SCARA_move_to_cal(90, 130);
+}
+
+/**
+ * M362: SCARA calibration: Move to cal-position PsiA (0 deg calibration)
+ */
+bool GcodeSuite::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)
+ */
+bool GcodeSuite::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)
+ */
+bool GcodeSuite::M364() {
+ SERIAL_ECHOLNPGM(" Cal: Theta-Psi 90");
+ return SCARA_move_to_cal(45, 135);
+}
+
+#endif // MORGAN_SCARA
diff --git a/Marlin/src/gcode/sd/M1001.cpp b/Marlin/src/gcode/sd/M1001.cpp
new file mode 100644
index 0000000..1cf700a
--- /dev/null
+++ b/Marlin/src/gcode/sd/M1001.cpp
@@ -0,0 +1,111 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../module/printcounter.h"
+
+#if DISABLED(NO_SD_AUTOSTART)
+ #include "../../sd/cardreader.h"
+#endif
+
+#ifdef SD_FINISHED_RELEASECOMMAND
+ #include "../queue.h"
+#endif
+
+#if EITHER(LCD_SET_PROGRESS_MANUALLY, SD_REPRINT_LAST_SELECTED_FILE)
+ #include "../../lcd/marlinui.h"
+#endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ #include "../../feature/powerloss.h"
+#endif
+
+#if HAS_LEDS_OFF_FLAG
+ #include "../../MarlinCore.h" // for wait_for_user_response()
+ #include "../../feature/leds/printer_event_leds.h"
+#endif
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../lcd/extui/ui_api.h"
+#endif
+
+#if ENABLED(HOST_ACTION_COMMANDS)
+ #include "../../feature/host_actions.h"
+#endif
+
+#ifndef PE_LEDS_COMPLETED_TIME
+ #define PE_LEDS_COMPLETED_TIME (30*60)
+#endif
+
+/**
+ * M1001: Execute actions for SD print completion
+ */
+void GcodeSuite::M1001() {
+ // If there's another auto#.g file to run...
+ if (TERN(NO_SD_AUTOSTART, false, card.autofile_check())) return;
+
+ // Purge the recovery file...
+ TERN_(POWER_LOSS_RECOVERY, recovery.purge());
+
+ // Report total print time
+ const bool long_print = print_job_timer.duration() > 60;
+ if (long_print) gcode.process_subcommands_now_P(PSTR("M31"));
+
+ // Stop the print job timer
+ gcode.process_subcommands_now_P(PSTR("M77"));
+
+ // Set the progress bar "done" state
+ TERN_(LCD_SET_PROGRESS_MANUALLY, ui.set_progress_done());
+
+ // Announce SD file completion
+ {
+ PORT_REDIRECT(SERIAL_ALL);
+ SERIAL_ECHOLNPGM(STR_FILE_PRINTED);
+ }
+
+ // Update the status LED color
+ #if HAS_LEDS_OFF_FLAG
+ if (long_print) {
+ printerEventLEDs.onPrintCompleted();
+ TERN_(EXTENSIBLE_UI, ExtUI::onUserConfirmRequired_P(GET_TEXT(MSG_PRINT_DONE)));
+ TERN_(HOST_PROMPT_SUPPORT, host_prompt_do(PROMPT_USER_CONTINUE, GET_TEXT(MSG_PRINT_DONE), CONTINUE_STR));
+ wait_for_user_response(SEC_TO_MS(TERN(HAS_LCD_MENU, PE_LEDS_COMPLETED_TIME, 30)));
+ printerEventLEDs.onResumeAfterWait();
+ }
+ #endif
+
+ // Inject SD_FINISHED_RELEASECOMMAND, if any
+ #ifdef SD_FINISHED_RELEASECOMMAND
+ gcode.process_subcommands_now_P(PSTR(SD_FINISHED_RELEASECOMMAND));
+ #endif
+
+ TERN_(EXTENSIBLE_UI, ExtUI::onPrintFinished());
+
+ // Re-select the last printed file in the UI
+ TERN_(SD_REPRINT_LAST_SELECTED_FILE, ui.reselect_last_file());
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M20.cpp b/Marlin/src/gcode/sd/M20.cpp
new file mode 100644
index 0000000..7ac4aff
--- /dev/null
+++ b/Marlin/src/gcode/sd/M20.cpp
@@ -0,0 +1,43 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M20: List SD card to serial output
+ */
+void GcodeSuite::M20() {
+ if (card.flag.mounted) {
+ SERIAL_ECHOLNPGM(STR_BEGIN_FILE_LIST);
+ card.ls();
+ SERIAL_ECHOLNPGM(STR_END_FILE_LIST);
+ }
+ else
+ SERIAL_ECHO_MSG(STR_NO_MEDIA);
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M21_M22.cpp b/Marlin/src/gcode/sd/M21_M22.cpp
new file mode 100644
index 0000000..f42784d
--- /dev/null
+++ b/Marlin/src/gcode/sd/M21_M22.cpp
@@ -0,0 +1,44 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M21: Init SD Card
+ */
+void GcodeSuite::M21() { card.mount(); }
+
+/**
+ * M22: Release SD Card
+ */
+void GcodeSuite::M22() {
+ if (!IS_SD_PRINTING()) card.release();
+ IF_ENABLED(TFT_COLOR_UI, ui.refresh(LCDVIEW_CALL_REDRAW_NEXT));
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M23.cpp b/Marlin/src/gcode/sd/M23.cpp
new file mode 100644
index 0000000..51bc824
--- /dev/null
+++ b/Marlin/src/gcode/sd/M23.cpp
@@ -0,0 +1,44 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M23: Open a file
+ *
+ * The path is relative to the root directory
+ */
+void GcodeSuite::M23() {
+ // Simplify3D includes the size, so zero out all spaces (#7227)
+ for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0';
+ card.openFileRead(parser.string_arg);
+
+ TERN_(LCD_SET_PROGRESS_MANUALLY, ui.set_progress(0));
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M24_M25.cpp b/Marlin/src/gcode/sd/M24_M25.cpp
new file mode 100644
index 0000000..611ba17
--- /dev/null
+++ b/Marlin/src/gcode/sd/M24_M25.cpp
@@ -0,0 +1,115 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+#include "../../module/printcounter.h"
+#include "../../lcd/marlinui.h"
+
+#if ENABLED(PARK_HEAD_ON_PAUSE)
+ #include "../../feature/pause.h"
+#endif
+
+#if ENABLED(HOST_ACTION_COMMANDS)
+ #include "../../feature/host_actions.h"
+#endif
+
+#if ENABLED(POWER_LOSS_RECOVERY)
+ #include "../../feature/powerloss.h"
+#endif
+
+#include "../../MarlinCore.h" // for startOrResumeJob
+
+/**
+ * M24: Start or Resume SD Print
+ */
+void GcodeSuite::M24() {
+
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ if (parser.seenval('S')) card.setIndex(parser.value_long());
+ if (parser.seenval('T')) print_job_timer.resume(parser.value_long());
+ #endif
+
+ #if ENABLED(PARK_HEAD_ON_PAUSE)
+ if (did_pause_print) {
+ resume_print(); // will call print_job_timer.start()
+ return;
+ }
+ #endif
+
+ if (card.isFileOpen()) {
+ card.startFileprint(); // SD card will now be read for commands
+ startOrResumeJob(); // Start (or resume) the print job timer
+ TERN_(POWER_LOSS_RECOVERY, recovery.prepare());
+ }
+
+ #if ENABLED(HOST_ACTION_COMMANDS)
+ #ifdef ACTION_ON_RESUME
+ host_action_resume();
+ #endif
+ TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_INFO, PSTR("Resuming SD"), DISMISS_STR));
+ #endif
+
+ ui.reset_status();
+}
+
+/**
+ * M25: Pause SD Print
+ */
+void GcodeSuite::M25() {
+
+ #if ENABLED(PARK_HEAD_ON_PAUSE)
+
+ M125();
+
+ #else
+
+ // Set initial pause flag to prevent more commands from landing in the queue while we try to pause
+ #if ENABLED(SDSUPPORT)
+ if (IS_SD_PRINTING()) card.pauseSDPrint();
+ #endif
+
+ #if ENABLED(POWER_LOSS_RECOVERY)
+ if (recovery.enabled) recovery.save(true);
+ #endif
+
+ print_job_timer.pause();
+
+ #if DISABLED(DWIN_CREALITY_LCD)
+ ui.reset_status();
+ #endif
+
+ #if ENABLED(HOST_ACTION_COMMANDS)
+ TERN_(HOST_PROMPT_SUPPORT, host_prompt_open(PROMPT_PAUSE_RESUME, PSTR("Pause SD"), PSTR("Resume")));
+ #ifdef ACTION_ON_PAUSE
+ host_action_pause();
+ #endif
+ #endif
+
+ #endif
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M26.cpp b/Marlin/src/gcode/sd/M26.cpp
new file mode 100644
index 0000000..e0557bf
--- /dev/null
+++ b/Marlin/src/gcode/sd/M26.cpp
@@ -0,0 +1,38 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M26: Set SD Card file index
+ */
+void GcodeSuite::M26() {
+ if (card.isMounted() && parser.seenval('S'))
+ card.setIndex(parser.value_long());
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M27.cpp b/Marlin/src/gcode/sd/M27.cpp
new file mode 100644
index 0000000..a76070f
--- /dev/null
+++ b/Marlin/src/gcode/sd/M27.cpp
@@ -0,0 +1,52 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M27: Get SD Card status
+ * OR, with 'S<seconds>' set the SD status auto-report interval. (Requires AUTO_REPORT_SD_STATUS)
+ * OR, with 'C' get the current filename.
+ */
+void GcodeSuite::M27() {
+ if (parser.seen('C')) {
+ SERIAL_ECHOPGM("Current file: ");
+ card.printFilename();
+ return;
+ }
+
+ #if ENABLED(AUTO_REPORT_SD_STATUS)
+ if (parser.seenval('S')) {
+ card.auto_reporter.set_interval(parser.value_byte());
+ return;
+ }
+ #endif
+
+ card.report_status();
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M28_M29.cpp b/Marlin/src/gcode/sd/M28_M29.cpp
new file mode 100644
index 0000000..6f3f245
--- /dev/null
+++ b/Marlin/src/gcode/sd/M28_M29.cpp
@@ -0,0 +1,72 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+#if HAS_MULTI_SERIAL
+ #include "../queue.h"
+#endif
+
+/**
+ * M28: Start SD Write
+ */
+void GcodeSuite::M28() {
+
+ #if ENABLED(BINARY_FILE_TRANSFER)
+
+ bool binary_mode = false;
+ char *p = parser.string_arg;
+ if (p[0] == 'B' && NUMERIC(p[1])) {
+ binary_mode = p[1] > '0';
+ p += 2;
+ while (*p == ' ') ++p;
+ }
+
+ // Binary transfer mode
+ if ((card.flag.binary_mode = binary_mode)) {
+ SERIAL_ECHO_MSG("Switching to Binary Protocol");
+ TERN_(HAS_MULTI_SERIAL, card.transfer_port_index = queue.port[queue.index_r]);
+ }
+ else
+ card.openFileWrite(p);
+
+ #else
+
+ card.openFileWrite(parser.string_arg);
+
+ #endif
+}
+
+/**
+ * M29: Stop SD Write
+ * (Processed in write-to-file routine)
+ */
+void GcodeSuite::M29() {
+ card.flag.saving = false;
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M30.cpp b/Marlin/src/gcode/sd/M30.cpp
new file mode 100644
index 0000000..b95a895
--- /dev/null
+++ b/Marlin/src/gcode/sd/M30.cpp
@@ -0,0 +1,40 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M30 <filename>: Delete SD Card file
+ */
+void GcodeSuite::M30() {
+ if (card.isMounted()) {
+ card.closefile();
+ card.removeFile(parser.string_arg);
+ }
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M32.cpp b/Marlin/src/gcode/sd/M32.cpp
new file mode 100644
index 0000000..ea893c9
--- /dev/null
+++ b/Marlin/src/gcode/sd/M32.cpp
@@ -0,0 +1,59 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_MEDIA_SUBCALLS
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+#include "../../module/planner.h" // for synchronize()
+
+#include "../../MarlinCore.h" // for startOrResumeJob
+
+/**
+ * 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
+ */
+void GcodeSuite::M32() {
+ if (IS_SD_PRINTING()) planner.synchronize();
+
+ if (card.isMounted()) {
+ const uint8_t call_procedure = parser.boolval('P');
+
+ card.openFileRead(parser.string_arg, call_procedure);
+
+ if (parser.seenval('S')) card.setIndex(parser.value_long());
+
+ card.startFileprint();
+
+ // Procedure calls count as normal print time.
+ if (!call_procedure) startOrResumeJob();
+ }
+}
+
+#endif // HAS_MEDIA_SUBCALLS
diff --git a/Marlin/src/gcode/sd/M33.cpp b/Marlin/src/gcode/sd/M33.cpp
new file mode 100644
index 0000000..b611c8b
--- /dev/null
+++ b/Marlin/src/gcode/sd/M33.cpp
@@ -0,0 +1,48 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(LONG_FILENAME_HOST_SUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M33: Get the long full path of a file or folder
+ *
+ * Parameters:
+ * <dospath> Case-insensitive DOS-style path to a file or folder
+ *
+ * Example:
+ * M33 miscel~1/armchair/armcha~1.gco
+ *
+ * Output:
+ * /Miscellaneous/Armchair/Armchair.gcode
+ */
+void GcodeSuite::M33() {
+
+ card.printLongPath(parser.string_arg);
+
+}
+
+#endif // LONG_FILENAME_HOST_SUPPORT
diff --git a/Marlin/src/gcode/sd/M34.cpp b/Marlin/src/gcode/sd/M34.cpp
new file mode 100644
index 0000000..2dd7dc5
--- /dev/null
+++ b/Marlin/src/gcode/sd/M34.cpp
@@ -0,0 +1,42 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if BOTH(SDCARD_SORT_ALPHA, SDSORT_GCODE)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M34: Set SD Card Sorting Options
+ */
+void GcodeSuite::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
diff --git a/Marlin/src/gcode/sd/M524.cpp b/Marlin/src/gcode/sd/M524.cpp
new file mode 100644
index 0000000..089d2e2
--- /dev/null
+++ b/Marlin/src/gcode/sd/M524.cpp
@@ -0,0 +1,42 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M524: Abort the current SD print job (started with M24)
+ */
+void GcodeSuite::M524() {
+
+ if (IS_SD_PRINTING())
+ card.flag.abort_sd_printing = true;
+ else if (card.isMounted())
+ card.closefile();
+
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/sd/M808.cpp b/Marlin/src/gcode/sd/M808.cpp
new file mode 100644
index 0000000..0d11b16
--- /dev/null
+++ b/Marlin/src/gcode/sd/M808.cpp
@@ -0,0 +1,51 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(GCODE_REPEAT_MARKERS)
+
+#include "../gcode.h"
+#include "../../feature/repeat.h"
+
+/**
+ * M808: Set / Goto a repeat marker
+ *
+ * L<count> - Set a repeat marker with 'count' repetitions. If omitted, infinity.
+ *
+ * Examples:
+ *
+ * M808 L ; Set a loop marker with a count of infinity
+ * M808 L2 ; Set a loop marker with a count of 2
+ * M808 ; Decrement and loop if not zero.
+ */
+void GcodeSuite::M808() {
+
+ // Handled early and ignored here in the queue.
+ // Allowed to go into the queue for logging purposes.
+
+ // M808 K sent from the host to cancel all loops
+ if (parser.seen('K')) repeat.cancel();
+
+}
+
+#endif // GCODE_REPEAT_MARKERS
diff --git a/Marlin/src/gcode/sd/M928.cpp b/Marlin/src/gcode/sd/M928.cpp
new file mode 100644
index 0000000..03a7877
--- /dev/null
+++ b/Marlin/src/gcode/sd/M928.cpp
@@ -0,0 +1,39 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(SDSUPPORT)
+
+#include "../gcode.h"
+#include "../../sd/cardreader.h"
+
+/**
+ * M928: Start SD Logging
+ */
+void GcodeSuite::M928() {
+
+ card.openLogFile(parser.string_arg);
+
+}
+
+#endif // SDSUPPORT
diff --git a/Marlin/src/gcode/stats/M31.cpp b/Marlin/src/gcode/stats/M31.cpp
new file mode 100644
index 0000000..207f9e1
--- /dev/null
+++ b/Marlin/src/gcode/stats/M31.cpp
@@ -0,0 +1,40 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../core/serial.h"
+#include "../../module/printcounter.h"
+#include "../../libs/duration_t.h"
+#include "../../lcd/marlinui.h"
+
+/**
+ * M31: Get the time since the start of SD Print (or last M109)
+ */
+void GcodeSuite::M31() {
+ char buffer[22];
+ duration_t(print_job_timer.duration()).toString(buffer);
+
+ ui.set_status(buffer);
+
+ SERIAL_ECHO_START();
+ SERIAL_ECHOLNPAIR("Print time: ", buffer);
+}
diff --git a/Marlin/src/gcode/stats/M75-M78.cpp b/Marlin/src/gcode/stats/M75-M78.cpp
new file mode 100644
index 0000000..568d9b0
--- /dev/null
+++ b/Marlin/src/gcode/stats/M75-M78.cpp
@@ -0,0 +1,73 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/printcounter.h"
+#include "../../lcd/marlinui.h"
+
+#include "../../MarlinCore.h" // for startOrResumeJob
+
+/**
+ * M75: Start print timer
+ */
+void GcodeSuite::M75() {
+ startOrResumeJob();
+}
+
+/**
+ * M76: Pause print timer
+ */
+void GcodeSuite::M76() {
+ print_job_timer.pause();
+}
+
+/**
+ * M77: Stop print timer
+ */
+void GcodeSuite::M77() {
+ print_job_timer.stop();
+}
+
+#if ENABLED(PRINTCOUNTER)
+
+/**
+ * M78: Show print statistics
+ */
+void GcodeSuite::M78() {
+ if (parser.intval('S') == 78) { // "M78 S78" will reset the statistics
+ print_job_timer.initStats();
+ ui.reset_status();
+ return;
+ }
+
+ #if HAS_SERVICE_INTERVALS
+ if (parser.seenval('R')) {
+ print_job_timer.resetServiceInterval(parser.value_int());
+ ui.reset_status();
+ return;
+ }
+ #endif
+
+ print_job_timer.showStats();
+}
+
+#endif // PRINTCOUNTER
diff --git a/Marlin/src/gcode/temp/M104_M109.cpp b/Marlin/src/gcode/temp/M104_M109.cpp
new file mode 100644
index 0000000..07e46e1
--- /dev/null
+++ b/Marlin/src/gcode/temp/M104_M109.cpp
@@ -0,0 +1,200 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * gcode/temp/M104_M109.cpp
+ *
+ * Hotend target temperature control
+ */
+
+#include "../../inc/MarlinConfigPre.h"
+
+#if EXTRUDERS
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+#include "../../module/motion.h"
+#include "../../module/planner.h"
+#include "../../lcd/marlinui.h"
+
+#include "../../MarlinCore.h" // for startOrResumeJob, etc.
+
+#if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ #include "../../module/printcounter.h"
+ #if ENABLED(CANCEL_OBJECTS)
+ #include "../../feature/cancel_object.h"
+ #endif
+#endif
+
+#if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
+ #include "../../module/tool_change.h"
+#endif
+
+/**
+ * M104: Set Hotend Temperature target and return immediately
+ *
+ * Parameters:
+ * I<preset> : Material Preset index (if material presets are defined)
+ * T<index> : Tool index. If omitted, applies to the active tool
+ * S<target> : The target temperature in current units
+ */
+void GcodeSuite::M104() {
+
+ if (DEBUGGING(DRYRUN)) return;
+
+ #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
+ constexpr int8_t target_extruder = 0;
+ #else
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ #endif
+
+ bool got_temp = false;
+ int16_t temp = 0;
+
+ // Accept 'I' if temperature presets are defined
+ #if PREHEAT_COUNT
+ got_temp = parser.seenval('I');
+ if (got_temp) {
+ const uint8_t index = parser.value_byte();
+ temp = ui.material_preset[_MIN(index, PREHEAT_COUNT - 1)].hotend_temp;
+ }
+ #endif
+
+ // If no 'I' get the temperature from 'S'
+ if (!got_temp) {
+ got_temp = parser.seenval('S');
+ if (got_temp) temp = parser.value_celsius();
+ }
+
+ if (got_temp) {
+ #if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
+ thermalManager.singlenozzle_temp[target_extruder] = temp;
+ if (target_extruder != active_extruder) return;
+ #endif
+ thermalManager.setTargetHotend(temp, target_extruder);
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ if (idex_is_duplicating() && 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.
+ * Hotends use EXTRUDE_MINTEMP / 2 to allow nozzles to be put into hot standby
+ * mode, for instance in a dual extruder setup, without affecting the running
+ * print timer.
+ */
+ thermalManager.auto_job_check_timer(false, true);
+ #endif
+ }
+
+ TERN_(AUTOTEMP, planner.autotemp_M104_M109());
+}
+
+/**
+ * M109: Set Hotend Temperature target and wait
+ *
+ * Parameters
+ * I<preset> : Material Preset index (if material presets are defined)
+ * T<index> : Tool index. If omitted, applies to the active tool
+ * S<target> : The target temperature in current units. Wait for heating only.
+ * R<target> : The target temperature in current units. Wait for heating and cooling.
+ *
+ * With AUTOTEMP...
+ * F<factor> : Autotemp Scaling Factor. Set non-zero to enable Auto-temp.
+ * S<min> : Minimum temperature, in current units.
+ * B<max> : Maximum temperature, in current units.
+ *
+ * Examples
+ * M109 S100 : Set target to 100°. Wait until the hotend is at or above 100°.
+ * M109 R150 : Set target to 150°. Wait until the hotend gets close to 150°.
+ *
+ * With PRINTJOB_TIMER_AUTOSTART turning on heaters will start the print job timer
+ * (used by printingIsActive, etc.) and turning off heaters will stop the timer.
+ */
+void GcodeSuite::M109() {
+
+ if (DEBUGGING(DRYRUN)) return;
+
+ #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1
+ constexpr int8_t target_extruder = 0;
+ #else
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+ #endif
+
+ bool got_temp = false;
+ int16_t temp = 0;
+
+ // Accept 'I' if temperature presets are defined
+ #if PREHEAT_COUNT
+ got_temp = parser.seenval('I');
+ if (got_temp) {
+ const uint8_t index = parser.value_byte();
+ temp = ui.material_preset[_MIN(index, PREHEAT_COUNT - 1)].hotend_temp;
+ }
+ #endif
+
+ // Get the temperature from 'S' or 'R'
+ bool no_wait_for_cooling = false;
+ if (!got_temp) {
+ no_wait_for_cooling = parser.seenval('S');
+ got_temp = no_wait_for_cooling || parser.seenval('R');
+ if (got_temp) temp = int16_t(parser.value_celsius());
+ }
+
+ if (got_temp) {
+ #if ENABLED(SINGLENOZZLE_STANDBY_TEMP)
+ thermalManager.singlenozzle_temp[target_extruder] = temp;
+ if (target_extruder != active_extruder) return;
+ #endif
+ thermalManager.setTargetHotend(temp, target_extruder);
+
+ #if ENABLED(DUAL_X_CARRIAGE)
+ if (idex_is_duplicating() && 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.
+ */
+ thermalManager.auto_job_check_timer(true, true);
+ #endif
+
+ #if HAS_DISPLAY
+ if (thermalManager.isHeatingHotend(target_extruder) || !no_wait_for_cooling)
+ thermalManager.set_heating_message(target_extruder);
+ #endif
+ }
+
+ TERN_(AUTOTEMP, planner.autotemp_M104_M109());
+
+ if (got_temp)
+ (void)thermalManager.wait_for_hotend(target_extruder, no_wait_for_cooling);
+}
+
+#endif // EXTRUDERS
diff --git a/Marlin/src/gcode/temp/M105.cpp b/Marlin/src/gcode/temp/M105.cpp
new file mode 100644
index 0000000..eefc3ae
--- /dev/null
+++ b/Marlin/src/gcode/temp/M105.cpp
@@ -0,0 +1,51 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+
+/**
+ * M105: Read hot end and bed temperature
+ */
+void GcodeSuite::M105() {
+
+ const int8_t target_extruder = get_target_extruder_from_command();
+ if (target_extruder < 0) return;
+
+ SERIAL_ECHOPGM(STR_OK);
+
+ #if HAS_TEMP_SENSOR
+
+ thermalManager.print_heater_states(target_extruder
+ #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT)
+ , parser.boolval('R')
+ #endif
+ );
+
+ SERIAL_EOL();
+
+ #else
+
+ SERIAL_ECHOLNPGM(" T:0"); // Some hosts send M105 to test the serial connection
+
+ #endif
+}
diff --git a/Marlin/src/gcode/temp/M106_M107.cpp b/Marlin/src/gcode/temp/M106_M107.cpp
new file mode 100644
index 0000000..9c70f1e
--- /dev/null
+++ b/Marlin/src/gcode/temp/M106_M107.cpp
@@ -0,0 +1,95 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_FAN
+
+#include "../gcode.h"
+#include "../../module/motion.h"
+#include "../../module/temperature.h"
+
+#if PREHEAT_COUNT
+ #include "../../lcd/marlinui.h"
+#endif
+
+#if ENABLED(SINGLENOZZLE)
+ #define _ALT_P active_extruder
+ #define _CNT_P EXTRUDERS
+#else
+ #define _ALT_P _MIN(active_extruder, FAN_COUNT - 1)
+ #define _CNT_P FAN_COUNT
+#endif
+
+/**
+ * M106: Set Fan Speed
+ *
+ * I<index> Material Preset index (if material presets are defined)
+ * S<int> Speed between 0-255
+ * P<index> Fan index, if more than one fan
+ *
+ * With EXTRA_FAN_SPEED enabled:
+ *
+ * T<int> 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
+ */
+void GcodeSuite::M106() {
+ const uint8_t pfan = parser.byteval('P', _ALT_P);
+
+ if (pfan < _CNT_P) {
+
+ #if ENABLED(EXTRA_FAN_SPEED)
+ const uint16_t t = parser.intval('T');
+ if (t > 0) return thermalManager.set_temp_fan_speed(pfan, t);
+ #endif
+
+ const uint16_t dspeed = parser.seen('A') ? thermalManager.fan_speed[active_extruder] : 255;
+
+ uint16_t speed = dspeed;
+
+ // Accept 'I' if temperature presets are defined
+ #if PREHEAT_COUNT
+ const bool got_preset = parser.seenval('I');
+ if (got_preset) speed = ui.material_preset[_MIN(parser.value_byte(), PREHEAT_COUNT - 1)].fan_speed;
+ #else
+ constexpr bool got_preset = false;
+ #endif
+
+ if (!got_preset && parser.seenval('S'))
+ speed = parser.value_ushort();
+
+ // Set speed, with constraint
+ thermalManager.set_fan_speed(pfan, speed);
+ }
+}
+
+/**
+ * M107: Fan Off
+ */
+void GcodeSuite::M107() {
+ const uint8_t p = parser.byteval('P', _ALT_P);
+ thermalManager.set_fan_speed(p, 0);
+}
+
+#endif // HAS_FAN
diff --git a/Marlin/src/gcode/temp/M140_M190.cpp b/Marlin/src/gcode/temp/M140_M190.cpp
new file mode 100644
index 0000000..d684127
--- /dev/null
+++ b/Marlin/src/gcode/temp/M140_M190.cpp
@@ -0,0 +1,138 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * gcode/temp/M140_M190.cpp
+ *
+ * Bed target temperature control
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_HEATED_BED
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+#include "../../module/motion.h"
+#include "../../lcd/marlinui.h"
+
+#if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ #include "../../module/printcounter.h"
+#endif
+
+#if ENABLED(PRINTER_EVENT_LEDS)
+ #include "../../feature/leds/leds.h"
+#endif
+
+#include "../../MarlinCore.h" // for wait_for_heatup, idle, startOrResumeJob
+
+/**
+ * M140: Set bed temperature
+ *
+ * I<index> : Preset index (if material presets are defined)
+ * S<target> : The target temperature in current units
+ */
+void GcodeSuite::M140() {
+ if (DEBUGGING(DRYRUN)) return;
+
+ bool got_temp = false;
+ int16_t temp = 0;
+
+ // Accept 'I' if temperature presets are defined
+ #if PREHEAT_COUNT
+ got_temp = parser.seenval('I');
+ if (got_temp) {
+ const uint8_t index = parser.value_byte();
+ temp = ui.material_preset[_MIN(index, PREHEAT_COUNT - 1)].bed_temp;
+ }
+ #endif
+
+ // If no 'I' get the temperature from 'S'
+ if (!got_temp) {
+ got_temp = parser.seenval('S');
+ if (got_temp) temp = parser.value_celsius();
+ }
+
+ if (got_temp) {
+ thermalManager.setTargetBed(temp);
+
+ #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ /**
+ * Stop the timer at the end of print. Hotend, bed target, and chamber
+ * temperatures need to be set below mintemp. Order of M140, M104, and M141
+ * at the end of the print does not matter.
+ */
+ thermalManager.auto_job_check_timer(false, true);
+ #endif
+ }
+}
+
+/**
+ * M190 - Set Bed Temperature target and wait
+ *
+ * Parameters:
+ * I<index> : Preset index (if material presets are defined)
+ * S<target> : The target temperature in current units. Wait for heating only.
+ * R<target> : The target temperature in current units. Wait for heating and cooling.
+ *
+ * Examples:
+ * M190 S60 : Set target to 60°. Wait until the bed is at or above 60°.
+ * M190 R40 : Set target to 40°. Wait until the bed gets close to 40°.
+ *
+ * With PRINTJOB_TIMER_AUTOSTART turning on heaters will start the print job timer
+ * (used by printingIsActive, etc.) and turning off heaters will stop the timer.
+ */
+void GcodeSuite::M190() {
+ if (DEBUGGING(DRYRUN)) return;
+
+ bool got_temp = false;
+ int16_t temp = 0;
+
+ // Accept 'I' if temperature presets are defined
+ #if PREHEAT_COUNT
+ got_temp = parser.seenval('I');
+ if (got_temp) {
+ const uint8_t index = parser.value_byte();
+ temp = ui.material_preset[_MIN(index, PREHEAT_COUNT - 1)].bed_temp;
+ }
+ #endif
+
+ // Get the temperature from 'S' or 'R'
+ bool no_wait_for_cooling = false;
+ if (!got_temp) {
+ no_wait_for_cooling = parser.seenval('S');
+ got_temp = no_wait_for_cooling || parser.seenval('R');
+ if (got_temp) temp = int16_t(parser.value_celsius());
+ }
+
+ if (!got_temp) return;
+
+ thermalManager.setTargetBed(temp);
+
+ TERN_(PRINTJOB_TIMER_AUTOSTART, thermalManager.auto_job_check_timer(true, false));
+
+ ui.set_status_P(thermalManager.isHeatingBed() ? GET_TEXT(MSG_BED_HEATING) : GET_TEXT(MSG_BED_COOLING));
+
+ thermalManager.wait_for_bed(no_wait_for_cooling);
+}
+
+#endif // HAS_HEATED_BED
diff --git a/Marlin/src/gcode/temp/M141_M191.cpp b/Marlin/src/gcode/temp/M141_M191.cpp
new file mode 100644
index 0000000..17eb71e
--- /dev/null
+++ b/Marlin/src/gcode/temp/M141_M191.cpp
@@ -0,0 +1,89 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * gcode/temp/M141_M191.cpp
+ *
+ * Chamber target temperature control
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_HEATED_CHAMBER
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+
+#include "../../module/motion.h"
+#include "../../lcd/marlinui.h"
+
+#if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ #include "../../module/printcounter.h"
+#endif
+
+#if ENABLED(PRINTER_EVENT_LEDS)
+ #include "../../feature/leds/leds.h"
+#endif
+
+#include "../../MarlinCore.h" // for wait_for_heatup, idle, startOrResumeJob
+
+/**
+ * M141: Set chamber temperature
+ */
+void GcodeSuite::M141() {
+ if (DEBUGGING(DRYRUN)) return;
+ if (parser.seenval('S')) {
+ thermalManager.setTargetChamber(parser.value_celsius());
+
+ #if ENABLED(PRINTJOB_TIMER_AUTOSTART)
+ /**
+ * Stop the timer at the end of print. Hotend, bed target, and chamber
+ * temperatures need to be set below mintemp. Order of M140, M104, and M141
+ * at the end of the print does not matter.
+ */
+ thermalManager.auto_job_check_timer(false, true);
+ #endif
+ }
+}
+
+/**
+ * M191: Sxxx Wait for chamber current temp to reach target temp. Waits only when heating
+ * Rxxx Wait for chamber current temp to reach target temp. Waits when heating and cooling
+ */
+void GcodeSuite::M191() {
+ if (DEBUGGING(DRYRUN)) return;
+
+ const bool no_wait_for_cooling = parser.seenval('S');
+ if (no_wait_for_cooling || parser.seenval('R')) {
+ thermalManager.setTargetChamber(parser.value_celsius());
+ TERN_(PRINTJOB_TIMER_AUTOSTART, thermalManager.auto_job_check_timer(true, false));
+ }
+ else return;
+
+ const bool is_heating = thermalManager.isHeatingChamber();
+ if (is_heating || !no_wait_for_cooling) {
+ ui.set_status_P(is_heating ? GET_TEXT(MSG_CHAMBER_HEATING) : GET_TEXT(MSG_CHAMBER_COOLING));
+ thermalManager.wait_for_chamber(false);
+ }
+}
+
+#endif // HAS_HEATED_CHAMBER
diff --git a/Marlin/src/gcode/temp/M155.cpp b/Marlin/src/gcode/temp/M155.cpp
new file mode 100644
index 0000000..48c2398
--- /dev/null
+++ b/Marlin/src/gcode/temp/M155.cpp
@@ -0,0 +1,40 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if BOTH(AUTO_REPORT_TEMPERATURES, HAS_TEMP_SENSOR)
+
+#include "../gcode.h"
+#include "../../module/temperature.h"
+
+/**
+ * M155: Set temperature auto-report interval. M155 S<seconds>
+ */
+void GcodeSuite::M155() {
+
+ if (parser.seenval('S'))
+ thermalManager.auto_reporter.set_interval(parser.value_byte());
+
+}
+
+#endif // AUTO_REPORT_TEMPERATURES && HAS_TEMP_SENSOR
diff --git a/Marlin/src/gcode/temp/M303.cpp b/Marlin/src/gcode/temp/M303.cpp
new file mode 100644
index 0000000..a066ddc
--- /dev/null
+++ b/Marlin/src/gcode/temp/M303.cpp
@@ -0,0 +1,85 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if HAS_PID_HEATING
+
+#include "../gcode.h"
+#include "../../lcd/marlinui.h"
+#include "../../module/temperature.h"
+
+#if ENABLED(EXTENSIBLE_UI)
+ #include "../../lcd/extui/ui_api.h"
+#endif
+
+/**
+ * M303: PID relay autotune
+ *
+ * S<temperature> Set the target temperature. (Default: 150C / 70C)
+ * E<extruder> Extruder number to tune, or -1 for the bed. (Default: E0)
+ * C<cycles> Number of times to repeat the procedure. (Minimum: 3, Default: 5)
+ * U<bool> Flag to apply the result to the current PID values
+ *
+ * With PID_DEBUG:
+ * D Toggle PID debugging and EXIT without further action.
+ */
+
+#if ENABLED(PID_DEBUG)
+ bool pid_debug_flag = 0;
+#endif
+
+void GcodeSuite::M303() {
+
+ #if ENABLED(PID_DEBUG)
+ if (parser.seen('D')) {
+ pid_debug_flag = !pid_debug_flag;
+ SERIAL_ECHO_START();
+ SERIAL_ECHOPGM("PID Debug ");
+ serialprintln_onoff(pid_debug_flag);
+ return;
+ }
+ #endif
+
+ #define SI TERN(PIDTEMPBED, H_BED, H_E0)
+ #define EI TERN(PIDTEMP, HOTENDS - 1, H_BED)
+ const heater_id_t e = (heater_id_t)parser.intval('E');
+ if (!WITHIN(e, SI, EI)) {
+ SERIAL_ECHOLNPGM(STR_PID_BAD_EXTRUDER_NUM);
+ TERN_(EXTENSIBLE_UI, ExtUI::onPidTuning(ExtUI::result_t::PID_BAD_EXTRUDER_NUM));
+ return;
+ }
+
+ const int c = parser.intval('C', 5);
+ const bool u = parser.boolval('U');
+ const int16_t temp = parser.celsiusval('S', e < 0 ? PREHEAT_1_TEMP_BED : PREHEAT_1_TEMP_HOTEND);
+
+ #if DISABLED(BUSY_WHILE_HEATING)
+ KEEPALIVE_STATE(NOT_BUSY);
+ #endif
+
+ LCD_MESSAGEPGM(MSG_PID_AUTOTUNE);
+ thermalManager.PID_autotune(temp, e, c, u);
+ ui.reset_status();
+}
+
+#endif // HAS_PID_HEATING
diff --git a/Marlin/src/gcode/units/G20_G21.cpp b/Marlin/src/gcode/units/G20_G21.cpp
new file mode 100644
index 0000000..6f1d5ad
--- /dev/null
+++ b/Marlin/src/gcode/units/G20_G21.cpp
@@ -0,0 +1,39 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(INCH_MODE_SUPPORT)
+
+#include "../gcode.h"
+
+/**
+ * G20: Set input mode to inches
+ */
+void GcodeSuite::G20() { parser.set_input_linear_units(LINEARUNIT_INCH); }
+
+/**
+ * G21: Set input mode to millimeters
+ */
+void GcodeSuite::G21() { parser.set_input_linear_units(LINEARUNIT_MM); }
+
+#endif // INCH_MODE_SUPPORT
diff --git a/Marlin/src/gcode/units/M149.cpp b/Marlin/src/gcode/units/M149.cpp
new file mode 100644
index 0000000..5d9f832
--- /dev/null
+++ b/Marlin/src/gcode/units/M149.cpp
@@ -0,0 +1,38 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(TEMPERATURE_UNITS_SUPPORT)
+
+#include "../gcode.h"
+
+/**
+ * M149: Set temperature units
+ */
+void GcodeSuite::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 // TEMPERATURE_UNITS_SUPPORT
diff --git a/Marlin/src/gcode/units/M82_M83.cpp b/Marlin/src/gcode/units/M82_M83.cpp
new file mode 100644
index 0000000..d93f0ea
--- /dev/null
+++ b/Marlin/src/gcode/units/M82_M83.cpp
@@ -0,0 +1,33 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "../gcode.h"
+
+/**
+ * M82: Set E codes absolute (default)
+ */
+void GcodeSuite::M82() { set_e_absolute(); }
+
+/**
+ * M83: Set E codes relative while in Absolute Coordinates (G90) mode
+ */
+void GcodeSuite::M83() { set_e_relative(); }