... 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 .
+ *
+ */
+
+#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 = 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 .
+ *
+ */
+
+#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 .
+ *
+ */
+
+#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
+ * Set Z3 Only: M666 S3 Z
+ * Set Z4 Only: M666 S4 Z
+ * Set All: M666 Z
+ */
+ 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 .
+ *
+ */
+
+#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
--
cgit v1.2.3