diff --git a/Marlin/Marlin.ino b/Marlin/Marlin.ino index 49b6fd14b..9dd3f6907 100644 --- a/Marlin/Marlin.ino +++ b/Marlin/Marlin.ino @@ -28,7 +28,7 @@ * - https://github.com/simen/grbl/tree */ -#include "MarlinConfig.h" +#include "src/inc/MarlinConfig.h" #if ENABLED(ULTRA_LCD) #if ENABLED(LCD_I2C_TYPE_PCF8575) diff --git a/Marlin/src/Marlin.cpp b/Marlin/src/Marlin.cpp index ffe9cdc86..b14cf9eb7 100644 --- a/Marlin/src/Marlin.cpp +++ b/Marlin/src/Marlin.cpp @@ -243,42 +243,40 @@ #include "Marlin.h" -#include "ultralcd.h" -#include "planner.h" -#include "stepper.h" -#include "endstops.h" -#include "temperature.h" -#include "cardreader.h" -#include "configuration_store.h" -#include "language.h" +#include "lcd/ultralcd.h" +#include "module/planner.h" +#include "module/stepper.h" +#include "module/endstops.h" +#include "module/temperature.h" +#include "sd/cardreader.h" +#include "module/configuration_store.h" #ifdef ARDUINO - #include "pins_arduino.h" + #include #endif -#include "math.h" -#include "nozzle.h" -#include "duration_t.h" -#include "types.h" -#include "gcode.h" +#include +#include "libs/nozzle.h" +#include "libs/duration_t.h" +#include "gcode/parser.h" #if HAS_ABL - #include "vector_3.h" + #include "libs/vector_3.h" #if ENABLED(AUTO_BED_LEVELING_LINEAR) - #include "least_squares_fit.h" + #include "libs/least_squares_fit.h" #endif #elif ENABLED(MESH_BED_LEVELING) - #include "mesh_bed_leveling.h" + #include "feature/mbl/mesh_bed_leveling.h" #endif #if ENABLED(BEZIER_CURVE_SUPPORT) - #include "planner_bezier.h" + #include "module/planner_bezier.h" #endif #if HAS_BUZZER && DISABLED(LCD_USE_I2C_BUZZER) - #include "buzzer.h" + #include "libs/buzzer.h" #endif #if ENABLED(MAX7219_DEBUG) - #include "Max7219_Debug_LEDs.h" + #include "feature/leds/Max7219_Debug_LEDs.h" #endif #if ENABLED(NEOPIXEL_RGBW_LED) @@ -286,16 +284,15 @@ #endif #if ENABLED(BLINKM) - #include "blinkm.h" - #include "Wire.h" + #include "feature/leds/blinkm.h" #endif #if ENABLED(PCA9632) - #include "pca9632.h" + #include "feature/leds/pca9632.h" #endif #if HAS_SERVOS - #include "src/HAL/servo.h" + #include "HAL/servo.h" #endif #if HAS_DIGIPOTSS @@ -303,19 +300,19 @@ #endif #if ENABLED(DAC_STEPPER_CURRENT) - #include "stepper_dac.h" + #include "feature/dac/stepper_dac.h" #endif #if ENABLED(EXPERIMENTAL_I2CBUS) - #include "twibus.h" + #include "feature/twibus.h" #endif #if ENABLED(I2C_POSITION_ENCODERS) - #include "I2CPositionEncoder.h" + #include "feature/I2CPositionEncoder.h" #endif #if ENABLED(ENDSTOP_INTERRUPTS_FEATURE) - #include "src/HAL/HAL_endstop_interrupts.h" + #include "HAL/HAL_endstop_interrupts.h" #endif #if ENABLED(M100_FREE_MEMORY_WATCHER) @@ -337,7 +334,7 @@ #endif #if ENABLED(AUTO_BED_LEVELING_UBL) - #include "ubl.h" + #include "feature/ubl/ubl.h" extern bool defer_return_to_status; unified_bed_leveling ubl; #define UBL_MESH_VALID !( ( ubl.z_values[0][0] == ubl.z_values[0][1] && ubl.z_values[0][1] == ubl.z_values[0][2] \ @@ -662,10 +659,6 @@ static bool send_ok[BUFSIZE]; bool chdkActive = false; #endif -#ifdef AUTOMATIC_CURRENT_CONTROL - bool auto_current_control = 0; -#endif - #if ENABLED(PID_EXTRUSION_SCALING) int lpq_len = 20; #endif @@ -718,17 +711,11 @@ void prepare_move_to_destination(); void get_cartesian_from_steppers(); void set_current_from_steppers_for_axis(const AxisEnum axis); -#if ENABLED(ARC_SUPPORT) - void plan_arc(float target[XYZE], float* offset, uint8_t clockwise); -#endif - #if ENABLED(BEZIER_CURVE_SUPPORT) void plan_cubic_move(const float offset[4]); #endif -void tool_change(const uint8_t tmp_extruder, const float fr_mm_s=0.0, bool no_move=false); void report_current_position(); -void report_current_position_detail(); /** * sync_plan_position @@ -1177,7 +1164,7 @@ inline void get_serial_commands() { static bool stop_buffering = false, sd_comment_mode = false; - if (!card.sdprinting) return; + if (!IS_SD_PRINTING) return; /** * '#' stops reading from SD to the buffer prematurely, so procedural @@ -3324,130 +3311,11 @@ void gcode_get_destination() { ***************** GCode Handlers ***************** **************************************************/ -/** - * G0, G1: Coordinated movement of X Y Z E axes - */ -inline void gcode_G0_G1( - #if IS_SCARA - bool fast_move=false - #endif -) { - if (IsRunning()) { - gcode_get_destination(); // For X Y Z E F - - #if ENABLED(FWRETRACT) - if (MIN_AUTORETRACT <= MAX_AUTORETRACT) { - // When M209 Autoretract is enabled, convert E-only moves to firmware retract/recover moves - if (autoretract_enabled && parser.seen('E') && !(parser.seen('X') || parser.seen('Y') || parser.seen('Z'))) { - const float echange = destination[E_AXIS] - current_position[E_AXIS]; - // Is this a retract or recover move? - if (WITHIN(FABS(echange), MIN_AUTORETRACT, MAX_AUTORETRACT) && retracted[active_extruder] == (echange > 0.0)) { - current_position[E_AXIS] = destination[E_AXIS]; // Hide a G1-based retract/recover from calculations - sync_plan_position_e(); // AND from the planner - return retract(echange < 0.0); // Firmware-based retract/recover (double-retract ignored) - } - } - } - #endif // FWRETRACT - - #if IS_SCARA - fast_move ? prepare_uninterpolated_move_to_destination() : prepare_move_to_destination(); - #else - prepare_move_to_destination(); - #endif - } -} +#include "gcode/motion/G0_G1.h" -/** - * G2: Clockwise Arc - * G3: Counterclockwise Arc - * - * This command has two forms: IJ-form and R-form. - * - * - I specifies an X offset. J specifies a Y offset. - * At least one of the IJ parameters is required. - * X and Y can be omitted to do a complete circle. - * The given XY is not error-checked. The arc ends - * based on the angle of the destination. - * Mixing I or J with R will throw an error. - * - * - R specifies the radius. X or Y is required. - * Omitting both X and Y will throw an error. - * X or Y must differ from the current XY. - * Mixing R with I or J will throw an error. - * - * - P specifies the number of full circles to do - * before the specified arc move. - * - * Examples: - * - * G2 I10 ; CW circle centered at X+10 - * G3 X20 Y12 R14 ; CCW circle with r=14 ending at X20 Y12 - */ #if ENABLED(ARC_SUPPORT) - - inline void gcode_G2_G3(bool clockwise) { - if (IsRunning()) { - - #if ENABLED(SF_ARC_FIX) - const bool relative_mode_backup = relative_mode; - relative_mode = true; - #endif - - gcode_get_destination(); - - #if ENABLED(SF_ARC_FIX) - relative_mode = relative_mode_backup; - #endif - - float arc_offset[2] = { 0.0, 0.0 }; - if (parser.seenval('R')) { - const float r = parser.value_linear_units(), - p1 = current_position[X_AXIS], q1 = current_position[Y_AXIS], - p2 = destination[X_AXIS], q2 = destination[Y_AXIS]; - if (r && (p2 != p1 || q2 != q1)) { - const float e = clockwise ^ (r < 0) ? -1 : 1, // clockwise -1/1, counterclockwise 1/-1 - dx = p2 - p1, dy = q2 - q1, // X and Y differences - d = HYPOT(dx, dy), // Linear distance between the points - h = SQRT(sq(r) - sq(d * 0.5)), // Distance to the arc pivot-point - mx = (p1 + p2) * 0.5, my = (q1 + q2) * 0.5, // Point between the two points - sx = -dy / d, sy = dx / d, // Slope of the perpendicular bisector - cx = mx + e * h * sx, cy = my + e * h * sy; // Pivot-point of the arc - arc_offset[0] = cx - p1; - arc_offset[1] = cy - q1; - } - } - else { - if (parser.seenval('I')) arc_offset[0] = parser.value_linear_units(); - if (parser.seenval('J')) arc_offset[1] = parser.value_linear_units(); - } - - if (arc_offset[0] || arc_offset[1]) { - - #if ENABLED(ARC_P_CIRCLES) - // P indicates number of circles to do - int8_t circles_to_do = parser.byteval('P'); - if (!WITHIN(circles_to_do, 0, 100)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_ARC_ARGS); - } - while (circles_to_do--) - plan_arc(current_position, arc_offset, clockwise); - #endif - - // Send the arc to the planner - plan_arc(destination, arc_offset, clockwise); - refresh_cmd_timeout(); - } - else { - // Bad arguments - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_ARC_ARGS); - } - } - } - -#endif // ARC_SUPPORT + #include "gcode/motion/G2_G3.h" +#endif void dwell(millis_t time) { refresh_cmd_timeout(); @@ -3455,8140 +3323,551 @@ void dwell(millis_t time) { while (PENDING(millis(), time)) idle(); } -/** - * G4: Dwell S or P - */ -inline void gcode_G4() { - millis_t dwell_ms = 0; +#include "gcode/motion/G4.h" - 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 +#if ENABLED(BEZIER_CURVE_SUPPORT) + #include "gcode/motion/G5.h" +#endif - stepper.synchronize(); +#if ENABLED(FWRETRACT) + #include "gcode/feature/fwretract/G10_G11.h" +#endif - if (!lcd_hasstatus()) LCD_MESSAGEPGM(MSG_DWELL); +#if ENABLED(NOZZLE_CLEAN_FEATURE) + #include "gcode/feature/clean/G12.h" +#endif - dwell(dwell_ms); -} +#if ENABLED(CNC_WORKSPACE_PLANES) + #include "gcode/feature/clean/G17-G19.h" +#endif -#if ENABLED(BEZIER_CURVE_SUPPORT) +#if ENABLED(INCH_MODE_SUPPORT) + #include "gcode/units/G20_G21.h" +#endif - /** - * Parameters interpreted according to: - * http://linuxcnc.org/docs/2.6/html/gcode/gcode.html#sec:G5-Cubic-Spline - * However I, J omission is not supported at this point; all - * parameters can be omitted and default to zero. - */ +#if ENABLED(UBL_G26_MESH_VALIDATION) + #include "gcode/calibrate/G26.h" +#endif - /** - * G5: Cubic B-spline - */ - inline void gcode_G5() { - if (IsRunning()) { +#if ENABLED(NOZZLE_PARK_FEATURE) + #include "gcode/feature/pause/G27.h" +#endif - #if ENABLED(CNC_WORKSPACE_PLANES) - if (workspace_plane != PLANE_XY) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_BAD_PLANE_MODE); - return; - } - #endif +#if ENABLED(PROBE_MANUALLY) + bool g29_in_progress = false; +#else + constexpr bool g29_in_progress = false; +#endif - gcode_get_destination(); +#include "gcode/calibrate/G28.h" - const float offset[] = { - parser.linearval('I'), - parser.linearval('J'), - parser.linearval('P'), - parser.linearval('Q') - }; +void home_all_axes() { gcode_G28(true); } - plan_cubic_move(offset); - } +#if HAS_PROBING_PROCEDURE + + void out_of_range_error(const char* p_edge) { + SERIAL_PROTOCOLPGM("?Probe "); + serialprintPGM(p_edge); + SERIAL_PROTOCOLLNPGM(" position out of range."); } -#endif // BEZIER_CURVE_SUPPORT +#endif -#if ENABLED(FWRETRACT) +#include "gcode/calibrate/G29.h" - /** - * G10 - Retract filament according to settings of M207 - */ - inline void gcode_G10() { - #if EXTRUDERS > 1 - const bool rs = parser.boolval('S'); - retracted_swap[active_extruder] = rs; // Use 'S' for swap, default to false - #endif - retract(true - #if EXTRUDERS > 1 - , rs - #endif - ); - } +#if HAS_BED_PROBE + #include "gcode/probe/G30.h" + #if ENABLED(Z_PROBE_SLED) + #include "gcode/probe/G31_G32.h" + #endif +#endif - /** - * G11 - Recover filament according to settings of M208 - */ - inline void gcode_G11() { retract(false); } +#if PROBE_SELECTED && ENABLED(DELTA_AUTO_CALIBRATION) + #include "gcode/calibrate/G33.h" +#endif -#endif // FWRETRACT +#if ENABLED(G38_PROBE_TARGET) + #include "gcode/probe/G38.h" +#endif -#if ENABLED(NOZZLE_CLEAN_FEATURE) - /** - * G12: Clean the nozzle - */ - inline void gcode_G12() { - // Don't allow nozzle cleaning without homing first - if (axis_unhomed_error()) return; +#if HAS_MESH + #include "gcode/probe/G42.h" +#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.floatval('R', NOZZLE_CLEAN_CIRCLE_RADIUS); +#include "gcode/geometry/G92.h" - Nozzle::clean(pattern, strokes, radius, objects); - } +#if HAS_RESUME_CONTINUE + #include "gcode/lcd/M0_M1.h" #endif -#if ENABLED(CNC_WORKSPACE_PLANES) +#if ENABLED(SPINDLE_LASER_ENABLE) + #include "gcode/control/M3-M5.h" +#endif - void report_workspace_plane() { - SERIAL_ECHO_START(); - SERIAL_ECHOPGM("Workspace Plane "); - serialprintPGM(workspace_plane == PLANE_YZ ? PSTR("YZ\n") : workspace_plane == PLANE_ZX ? PSTR("ZX\n") : PSTR("XY\n")); - } +#include "gcode/control/M17.h" - /** - * G17: Select Plane XY - * G18: Select Plane ZX - * G19: Select Plane YZ - */ - inline void gcode_G17() { workspace_plane = PLANE_XY; } - inline void gcode_G18() { workspace_plane = PLANE_ZX; } - inline void gcode_G19() { workspace_plane = PLANE_YZ; } +#if ENABLED(ADVANCED_PAUSE_FEATURE) + // For M125, M600, M24 + #include "gcode/feature/pause/common.h" +#endif -#endif // CNC_WORKSPACE_PLANES +#if ENABLED(SDSUPPORT) + #include "gcode/sdcard/M20.h" // M20 - List SD card. (Requires SDSUPPORT) + #include "gcode/sdcard/M21.h" // M21 - Init SD card. (Requires SDSUPPORT) + #include "gcode/sdcard/M22.h" // M22 - Release SD card. (Requires SDSUPPORT) + #include "gcode/sdcard/M23.h" // M23 - Select SD file: "M23 /path/file.gco". (Requires SDSUPPORT) + #include "gcode/sdcard/M24.h" // M24 - Start/resume SD print. (Requires SDSUPPORT) + #include "gcode/sdcard/M25.h" // M25 - Pause SD print. (Requires SDSUPPORT) + #include "gcode/sdcard/M26.h" // M26 - Set SD position in bytes: "M26 S12345". (Requires SDSUPPORT) + #include "gcode/sdcard/M27.h" // M27 - Report SD print status. (Requires SDSUPPORT) + #include "gcode/sdcard/M28.h" // M28 - Start SD write: "M28 /path/file.gco". (Requires SDSUPPORT) + #include "gcode/sdcard/M29.h" // M29 - Stop SD write. (Requires SDSUPPORT) + #include "gcode/sdcard/M30.h" // M30 - Delete file from SD: "M30 /path/file.gco" +#endif -#if ENABLED(INCH_MODE_SUPPORT) - /** - * G20: Set input mode to inches - */ - inline void gcode_G20() { parser.set_input_linear_units(LINEARUNIT_INCH); } +#include "gcode/stats/M31.h" // M31: Get the time since the start of SD Print (or last M109) - /** - * G21: Set input mode to millimeters - */ - inline void gcode_G21() { parser.set_input_linear_units(LINEARUNIT_MM); } +#if ENABLED(SDSUPPORT) + #include "gcode/sdcard/M32.h" + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + #include "gcode/sdcard/M33.h" + #endif + #if ENABLED(SDCARD_SORT_ALPHA) && ENABLED(SDSORT_GCODE) + #include "gcode/sdcard/M34.h" + #endif + #include "gcode/sdcard/M928.h" #endif -#if ENABLED(NOZZLE_PARK_FEATURE) - /** - * G27: Park the nozzle - */ - inline void gcode_G27() { - // Don't allow nozzle parking without homing first - if (axis_unhomed_error()) return; - Nozzle::park(parser.ushortval('P')); - } -#endif // NOZZLE_PARK_FEATURE +/** + * Sensitive pin test for M42, M226 + */ +static bool pin_is_protected(const int8_t pin) { + static const int8_t sensitive_pins[] PROGMEM = SENSITIVE_PINS; + for (uint8_t i = 0; i < COUNT(sensitive_pins); i++) + if (pin == (int8_t)pgm_read_byte(&sensitive_pins[i])) return true; + return false; +} -#if ENABLED(QUICK_HOME) +#include "gcode/control/M42.h" - static void quick_home_xy() { +#if ENABLED(PINS_DEBUGGING) + #include "gcode/config/M43.h" +#endif - // Pretend the current position is 0,0 - current_position[X_AXIS] = current_position[Y_AXIS] = 0.0; - sync_plan_position(); +#if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) + #include "gcode/calibrate/M48.h" +#endif - const int x_axis_home_dir = - #if ENABLED(DUAL_X_CARRIAGE) - x_home_dir(active_extruder) - #else - home_dir(X_AXIS) - #endif - ; +#if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(UBL_G26_MESH_VALIDATION) + #include "gcode/calibrate/M49.h" +#endif - const float mlx = max_length(X_AXIS), - mly = max_length(Y_AXIS), - mlratio = mlx > mly ? mly / mlx : mlx / mly, - fr_mm_s = min(homing_feedrate(X_AXIS), homing_feedrate(Y_AXIS)) * SQRT(sq(mlratio) + 1.0); +#include "gcode/stats/M75.h" +#include "gcode/stats/M76.h" +#include "gcode/stats/M77.h" - do_blocking_move_to_xy(1.5 * mlx * x_axis_home_dir, 1.5 * mly * home_dir(Y_AXIS), fr_mm_s); - endstops.hit_on_purpose(); // clear endstop hit flags - current_position[X_AXIS] = current_position[Y_AXIS] = 0.0; - } +#if ENABLED(PRINTCOUNTER) + #include "gcode/stats/M78.h" +#endif -#endif // QUICK_HOME +#include "gcode/temperature/M104.h" -#if ENABLED(DEBUG_LEVELING_FEATURE) +#if HAS_TEMP_HOTEND || HAS_TEMP_BED - void log_machine_info() { - SERIAL_ECHOPGM("Machine Type: "); - #if ENABLED(DELTA) - SERIAL_ECHOLNPGM("Delta"); - #elif IS_SCARA - SERIAL_ECHOLNPGM("SCARA"); - #elif IS_CORE - SERIAL_ECHOLNPGM("Core"); - #else - SERIAL_ECHOLNPGM("Cartesian"); + void print_heater_state(const float &c, const float &t, + #if ENABLED(SHOW_TEMP_ADC_VALUES) + const float r, #endif - - SERIAL_ECHOPGM("Probe: "); - #if ENABLED(PROBE_MANUALLY) - SERIAL_ECHOLNPGM("PROBE_MANUALLY"); - #elif ENABLED(FIX_MOUNTED_PROBE) - SERIAL_ECHOLNPGM("FIX_MOUNTED_PROBE"); - #elif ENABLED(BLTOUCH) - SERIAL_ECHOLNPGM("BLTOUCH"); - #elif HAS_Z_SERVO_ENDSTOP - SERIAL_ECHOLNPGM("SERVO PROBE"); - #elif ENABLED(Z_PROBE_SLED) - SERIAL_ECHOLNPGM("Z_PROBE_SLED"); - #elif ENABLED(Z_PROBE_ALLEN_KEY) - SERIAL_ECHOLNPGM("Z_PROBE_ALLEN_KEY"); - #else - SERIAL_ECHOLNPGM("NONE"); + const int8_t e=-2 + ) { + #if !(HAS_TEMP_BED && HAS_TEMP_HOTEND) && HOTENDS <= 1 + UNUSED(e); #endif - #if HAS_BED_PROBE - SERIAL_ECHOPAIR("Probe Offset X:", X_PROBE_OFFSET_FROM_EXTRUDER); - SERIAL_ECHOPAIR(" Y:", Y_PROBE_OFFSET_FROM_EXTRUDER); - SERIAL_ECHOPAIR(" Z:", zprobe_zoffset); - #if X_PROBE_OFFSET_FROM_EXTRUDER > 0 - SERIAL_ECHOPGM(" (Right"); - #elif X_PROBE_OFFSET_FROM_EXTRUDER < 0 - SERIAL_ECHOPGM(" (Left"); - #elif Y_PROBE_OFFSET_FROM_EXTRUDER != 0 - SERIAL_ECHOPGM(" (Middle"); + SERIAL_PROTOCOLCHAR(' '); + SERIAL_PROTOCOLCHAR( + #if HAS_TEMP_BED && HAS_TEMP_HOTEND + e == -1 ? 'B' : 'T' + #elif HAS_TEMP_HOTEND + 'T' #else - SERIAL_ECHOPGM(" (Aligned With"); - #endif - #if Y_PROBE_OFFSET_FROM_EXTRUDER > 0 - SERIAL_ECHOPGM("-Back"); - #elif Y_PROBE_OFFSET_FROM_EXTRUDER < 0 - SERIAL_ECHOPGM("-Front"); - #elif X_PROBE_OFFSET_FROM_EXTRUDER != 0 - SERIAL_ECHOPGM("-Center"); + 'B' #endif - if (zprobe_zoffset < 0) - SERIAL_ECHOPGM(" & Below"); - else if (zprobe_zoffset > 0) - SERIAL_ECHOPGM(" & Above"); - else - SERIAL_ECHOPGM(" & Same Z as"); - SERIAL_ECHOLNPGM(" Nozzle)"); + ); + #if HOTENDS > 1 + if (e >= 0) SERIAL_PROTOCOLCHAR('0' + e); + #endif + SERIAL_PROTOCOLCHAR(':'); + SERIAL_PROTOCOL(c); + SERIAL_PROTOCOLPAIR(" /" , t); + #if ENABLED(SHOW_TEMP_ADC_VALUES) + SERIAL_PROTOCOLPAIR(" (", r / OVERSAMPLENR); + SERIAL_PROTOCOLCHAR(')'); #endif + } - #if HAS_ABL - SERIAL_ECHOPGM("Auto Bed Leveling: "); - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - SERIAL_ECHOPGM("LINEAR"); - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - SERIAL_ECHOPGM("BILINEAR"); - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - SERIAL_ECHOPGM("3POINT"); - #elif ENABLED(AUTO_BED_LEVELING_UBL) - SERIAL_ECHOPGM("UBL"); - #endif - if (leveling_is_active()) { - SERIAL_ECHOLNPGM(" (enabled)"); - #if ABL_PLANAR - const float diff[XYZ] = { - stepper.get_axis_position_mm(X_AXIS) - current_position[X_AXIS], - stepper.get_axis_position_mm(Y_AXIS) - current_position[Y_AXIS], - stepper.get_axis_position_mm(Z_AXIS) - current_position[Z_AXIS] - }; - SERIAL_ECHOPGM("ABL Adjustment X"); - if (diff[X_AXIS] > 0) SERIAL_CHAR('+'); - SERIAL_ECHO(diff[X_AXIS]); - SERIAL_ECHOPGM(" Y"); - if (diff[Y_AXIS] > 0) SERIAL_CHAR('+'); - SERIAL_ECHO(diff[Y_AXIS]); - SERIAL_ECHOPGM(" Z"); - if (diff[Z_AXIS] > 0) SERIAL_CHAR('+'); - SERIAL_ECHO(diff[Z_AXIS]); - #elif ENABLED(AUTO_BED_LEVELING_UBL) - SERIAL_ECHOPAIR("UBL Adjustment Z", stepper.get_axis_position_mm(Z_AXIS) - current_position[Z_AXIS]); - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - SERIAL_ECHOPAIR("ABL Adjustment Z", bilinear_z_offset(current_position)); + void print_heaterstates() { + #if HAS_TEMP_HOTEND + print_heater_state(thermalManager.degHotend(target_extruder), thermalManager.degTargetHotend(target_extruder) + #if ENABLED(SHOW_TEMP_ADC_VALUES) + , thermalManager.rawHotendTemp(target_extruder) + #endif + ); + #endif + #if HAS_TEMP_BED + print_heater_state(thermalManager.degBed(), thermalManager.degTargetBed(), + #if ENABLED(SHOW_TEMP_ADC_VALUES) + thermalManager.rawBedTemp(), + #endif + -1 // BED + ); + #endif + #if HOTENDS > 1 + HOTEND_LOOP() print_heater_state(thermalManager.degHotend(e), thermalManager.degTargetHotend(e), + #if ENABLED(SHOW_TEMP_ADC_VALUES) + thermalManager.rawHotendTemp(e), #endif + e + ); + #endif + SERIAL_PROTOCOLPGM(" @:"); + SERIAL_PROTOCOL(thermalManager.getHeaterPower(target_extruder)); + #if HAS_TEMP_BED + SERIAL_PROTOCOLPGM(" B@:"); + SERIAL_PROTOCOL(thermalManager.getHeaterPower(-1)); + #endif + #if HOTENDS > 1 + HOTEND_LOOP() { + SERIAL_PROTOCOLPAIR(" @", e); + SERIAL_PROTOCOLCHAR(':'); + SERIAL_PROTOCOL(thermalManager.getHeaterPower(e)); } - else - SERIAL_ECHOLNPGM(" (disabled)"); + #endif + } - SERIAL_EOL(); +#endif // HAS_TEMP_HOTEND || HAS_TEMP_BED - #elif ENABLED(MESH_BED_LEVELING) +#include "gcode/temperature/M105.h" - SERIAL_ECHOPGM("Mesh Bed Leveling"); - if (leveling_is_active()) { - float lz = current_position[Z_AXIS]; - planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], lz); - SERIAL_ECHOLNPGM(" (enabled)"); - SERIAL_ECHOPAIR("MBL Adjustment Z", lz); - } - else - SERIAL_ECHOPGM(" (disabled)"); +#if ENABLED(AUTO_REPORT_TEMPERATURES) && (HAS_TEMP_HOTEND || HAS_TEMP_BED) - SERIAL_EOL(); + static uint8_t auto_report_temp_interval; + static millis_t next_temp_report_ms; - #endif // MESH_BED_LEVELING + inline void auto_report_temperatures() { + if (auto_report_temp_interval && ELAPSED(millis(), next_temp_report_ms)) { + next_temp_report_ms = millis() + 1000UL * auto_report_temp_interval; + print_heaterstates(); + SERIAL_EOL(); + } } -#endif // DEBUG_LEVELING_FEATURE + #include "gcode/temperature/M155.h" -#if ENABLED(DELTA) +#endif // AUTO_REPORT_TEMPERATURES && (HAS_TEMP_HOTEND || HAS_TEMP_BED) - /** - * A delta can only safely home all axes at the same time - * This is like quick_home_xy() but for 3 towers. - */ - inline bool home_delta() { - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS(">>> home_delta", current_position); - #endif - // Init the current position of all carriages to 0,0,0 - ZERO(current_position); - sync_plan_position(); - - // Move all carriages together linearly until an endstop is hit. - current_position[X_AXIS] = current_position[Y_AXIS] = current_position[Z_AXIS] = (DELTA_HEIGHT + home_offset[Z_AXIS] + 10); - feedrate_mm_s = homing_feedrate(X_AXIS); - line_to_current_position(); - stepper.synchronize(); - - // If an endstop was not hit, then damage can occur if homing is continued. - // This can occur if the delta height (DELTA_HEIGHT + home_offset[Z_AXIS]) is - // not set correctly. - if (!(Endstops::endstop_hit_bits & (_BV(X_MAX) | _BV(Y_MAX) | _BV(Z_MAX)))) { - LCD_MESSAGEPGM(MSG_ERR_HOMING_FAILED); - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_HOMING_FAILED); - return false; - } - - endstops.hit_on_purpose(); // clear endstop hit flags - - // At least one carriage has reached the top. - // Now re-home each carriage separately. - HOMEAXIS(A); - HOMEAXIS(B); - HOMEAXIS(C); - - // Set all carriages to their home positions - // Do this here all at once for Delta, because - // XYZ isn't ABC. Applying this per-tower would - // give the impression that they are the same. - LOOP_XYZ(i) set_axis_is_at_home((AxisEnum)i); - - SYNC_PLAN_POSITION_KINEMATIC(); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("<<< home_delta", current_position); - #endif - - return true; - } - -#endif // DELTA - -#if ENABLED(Z_SAFE_HOMING) - - inline void home_z_safely() { - - // Disallow Z homing if X or Y are unknown - if (!axis_known_position[X_AXIS] || !axis_known_position[Y_AXIS]) { - LCD_MESSAGEPGM(MSG_ERR_Z_HOMING); - SERIAL_ECHO_START(); - SERIAL_ECHOLNPGM(MSG_ERR_Z_HOMING); - return; - } - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Z_SAFE_HOMING >>>"); - #endif - - SYNC_PLAN_POSITION_KINEMATIC(); - - /** - * Move the Z probe (or just the nozzle) to the safe homing point - */ - destination[X_AXIS] = LOGICAL_X_POSITION(Z_SAFE_HOMING_X_POINT); - destination[Y_AXIS] = LOGICAL_Y_POSITION(Z_SAFE_HOMING_Y_POINT); - destination[Z_AXIS] = current_position[Z_AXIS]; // Z is already at the right height - - #if HOMING_Z_WITH_PROBE - destination[X_AXIS] -= X_PROBE_OFFSET_FROM_EXTRUDER; - destination[Y_AXIS] -= Y_PROBE_OFFSET_FROM_EXTRUDER; - #endif - - if (position_is_reachable_xy(destination[X_AXIS], destination[Y_AXIS])) { - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("Z_SAFE_HOMING", destination); - #endif - - // This causes the carriage on Dual X to unpark - #if ENABLED(DUAL_X_CARRIAGE) - active_extruder_parked = false; - #endif - - do_blocking_move_to_xy(destination[X_AXIS], destination[Y_AXIS]); - HOMEAXIS(Z); - } - else { - LCD_MESSAGEPGM(MSG_ZPROBE_OUT); - SERIAL_ECHO_START(); - SERIAL_ECHOLNPGM(MSG_ZPROBE_OUT); - } - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< Z_SAFE_HOMING"); - #endif - } - -#endif // Z_SAFE_HOMING - -#if ENABLED(PROBE_MANUALLY) - bool g29_in_progress = false; -#else - constexpr bool g29_in_progress = false; +#if FAN_COUNT > 0 + #include "gcode/temperature/M106.h" + #include "gcode/temperature/M107.h" #endif -/** - * G28: Home all axes according to settings - * - * Parameters - * - * None Home to all axes with no parameters. - * With QUICK_HOME enabled XY will home together, then Z. - * - * Cartesian parameters - * - * X Home to the X endstop - * Y Home to the Y endstop - * Z Home to the Z endstop - * - */ -inline void gcode_G28(const bool always_home_all) { - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPGM(">>> gcode_G28"); - log_machine_info(); - } - #endif - - // Wait for planner moves to finish! - stepper.synchronize(); - - // Cancel the active G29 session - #if ENABLED(PROBE_MANUALLY) - g29_in_progress = false; - #endif - - // Disable the leveling matrix before homing - #if HAS_LEVELING - #if ENABLED(AUTO_BED_LEVELING_UBL) - const bool ubl_state_at_entry = leveling_is_active(); - #endif - set_bed_leveling_enabled(false); - #endif - - #if ENABLED(CNC_WORKSPACE_PLANES) - workspace_plane = PLANE_XY; - #endif - - // Always home with tool 0 active - #if HOTENDS > 1 - const uint8_t old_tool_index = active_extruder; - tool_change(0, 0, true); - #endif - - #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) - extruder_duplication_enabled = false; - #endif - - setup_for_endstop_or_probe_move(); - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("> endstops.enable(true)"); - #endif - endstops.enable(true); // Enable endstops for next homing move - - #if ENABLED(DELTA) - - home_delta(); - UNUSED(always_home_all); - - #else // NOT DELTA - - const bool homeX = always_home_all || parser.seen('X'), - homeY = always_home_all || parser.seen('Y'), - homeZ = always_home_all || parser.seen('Z'), - home_all = (!homeX && !homeY && !homeZ) || (homeX && homeY && homeZ); - - set_destination_to_current(); - - #if Z_HOME_DIR > 0 // If homing away from BED do Z first - - if (home_all || homeZ) { - HOMEAXIS(Z); - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("> HOMEAXIS(Z)", current_position); - #endif - } - - #else - - if (home_all || homeX || homeY) { - // Raise Z before homing any other axes and z is not already high enough (never lower z) - destination[Z_AXIS] = LOGICAL_Z_POSITION(Z_HOMING_HEIGHT); - if (destination[Z_AXIS] > current_position[Z_AXIS]) { - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) - SERIAL_ECHOLNPAIR("Raise Z (before homing) to ", destination[Z_AXIS]); - #endif - - do_blocking_move_to_z(destination[Z_AXIS]); - } - } - - #endif - - #if ENABLED(QUICK_HOME) - - if (home_all || (homeX && homeY)) quick_home_xy(); - - #endif - - #if ENABLED(HOME_Y_BEFORE_X) - - // Home Y - if (home_all || homeY) { - HOMEAXIS(Y); - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("> homeY", current_position); - #endif - } - - #endif - - // Home X - if (home_all || homeX) { - - #if ENABLED(DUAL_X_CARRIAGE) - - // Always home the 2nd (right) extruder first - active_extruder = 1; - HOMEAXIS(X); - - // Remember this extruder's position for later tool change - inactive_extruder_x_pos = RAW_X_POSITION(current_position[X_AXIS]); - - // Home the 1st (left) extruder - active_extruder = 0; - HOMEAXIS(X); - - // Consider the active extruder to be parked - COPY(raised_parked_position, current_position); - delayed_move_time = 0; - active_extruder_parked = true; - - #else - - HOMEAXIS(X); - - #endif - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("> homeX", current_position); - #endif - } - - #if DISABLED(HOME_Y_BEFORE_X) - // Home Y - if (home_all || homeY) { - HOMEAXIS(Y); - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("> homeY", current_position); - #endif - } - #endif - - // Home Z last if homing towards the bed - #if Z_HOME_DIR < 0 - if (home_all || homeZ) { - #if ENABLED(Z_SAFE_HOMING) - home_z_safely(); - #else - HOMEAXIS(Z); - #endif - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("> (home_all || homeZ) > final", current_position); - #endif - } // home_all || homeZ - #endif // Z_HOME_DIR < 0 - - SYNC_PLAN_POSITION_KINEMATIC(); +#if DISABLED(EMERGENCY_PARSER) + #include "gcode/control/M108.h" + #include "gcode/control/M112.h" + #include "gcode/control/M410.h" +#endif - #endif // !DELTA (gcode_G28) +#include "gcode/temperature/M109.h" - endstops.not_homing(); +#if HAS_TEMP_BED + #include "gcode/temperature/M190.h" +#endif - #if ENABLED(DELTA) && ENABLED(DELTA_HOME_TO_SAFE_ZONE) - // move to a height where we can use the full xy-area - do_blocking_move_to_z(delta_clip_start_height); - #endif +#include "gcode/host/M110.h" - #if ENABLED(AUTO_BED_LEVELING_UBL) - set_bed_leveling_enabled(ubl_state_at_entry); - #endif +#include "gcode/control/M111.h" - clean_up_after_endstop_or_probe_move(); +#if ENABLED(HOST_KEEPALIVE_FEATURE) + #include "gcode/host/M113.h" +#endif - // Restore the active tool after homing - #if HOTENDS > 1 - tool_change(old_tool_index, 0, - #if ENABLED(PARKING_EXTRUDER) - false // fetch the previous toolhead - #else - true - #endif - ); +#if ENABLED(BARICUDA) + #if HAS_HEATER_1 + #include "gcode/feature/baricuda/M126.h" + #include "gcode/feature/baricuda/M127.h" #endif - - lcd_refresh(); - - report_current_position(); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< gcode_G28"); + #if HAS_HEATER_2 + #include "gcode/feature/baricuda/M128.h" + #include "gcode/feature/baricuda/M129.h" #endif -} // G28 - -void home_all_axes() { gcode_G28(true); } - -#if HAS_PROBING_PROCEDURE - - void out_of_range_error(const char* p_edge) { - SERIAL_PROTOCOLPGM("?Probe "); - serialprintPGM(p_edge); - SERIAL_PROTOCOLLNPGM(" position out of range."); - } - #endif -#if ENABLED(MESH_BED_LEVELING) || ENABLED(PROBE_MANUALLY) - - #if ENABLED(PROBE_MANUALLY) && ENABLED(LCD_BED_LEVELING) - extern bool lcd_wait_for_move; - #endif - - inline void _manual_goto_xy(const float &x, const float &y) { - const float old_feedrate_mm_s = feedrate_mm_s; - #if MANUAL_PROBE_HEIGHT > 0 - const float prev_z = current_position[Z_AXIS]; - feedrate_mm_s = homing_feedrate(Z_AXIS); - current_position[Z_AXIS] = LOGICAL_Z_POSITION(MANUAL_PROBE_HEIGHT); - line_to_current_position(); - #endif - - feedrate_mm_s = MMM_TO_MMS(XY_PROBE_SPEED); - current_position[X_AXIS] = LOGICAL_X_POSITION(x); - current_position[Y_AXIS] = LOGICAL_Y_POSITION(y); - line_to_current_position(); - - #if MANUAL_PROBE_HEIGHT > 0 - feedrate_mm_s = homing_feedrate(Z_AXIS); - current_position[Z_AXIS] = prev_z; // move back to the previous Z. - line_to_current_position(); - #endif - - feedrate_mm_s = old_feedrate_mm_s; - stepper.synchronize(); - - #if ENABLED(PROBE_MANUALLY) && ENABLED(LCD_BED_LEVELING) - lcd_wait_for_move = false; - #endif - } +#include "gcode/temperature/M140.h" +#if ENABLED(ULTIPANEL) + #include "gcode/lcd/M145.h" #endif -#if ENABLED(MESH_BED_LEVELING) - - // Save 130 bytes with non-duplication of PSTR - void echo_not_entered() { SERIAL_PROTOCOLLNPGM(" not entered."); } - - void mbl_mesh_report() { - SERIAL_PROTOCOLLNPGM("Num X,Y: " STRINGIFY(GRID_MAX_POINTS_X) "," STRINGIFY(GRID_MAX_POINTS_Y)); - SERIAL_PROTOCOLPGM("Z offset: "); SERIAL_PROTOCOL_F(mbl.z_offset, 5); - SERIAL_PROTOCOLLNPGM("\nMeasured points:"); - print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5, - [](const uint8_t ix, const uint8_t iy) { return mbl.z_values[ix][iy]; } - ); - } - - void mesh_probing_done() { - mbl.set_has_mesh(true); - home_all_axes(); - set_bed_leveling_enabled(true); - #if ENABLED(MESH_G28_REST_ORIGIN) - current_position[Z_AXIS] = LOGICAL_Z_POSITION(Z_MIN_POS); - set_destination_to_current(); - line_to_destination(homing_feedrate(Z_AXIS)); - stepper.synchronize(); - #endif - } - - /** - * G29: Mesh-based Z probe, probes a grid and produces a - * mesh to compensate for variable bed height - * - * Parameters With MESH_BED_LEVELING: - * - * S0 Produce a mesh report - * S1 Start probing mesh points - * S2 Probe the next mesh point - * S3 Xn Yn Zn.nn Manually modify a single point - * S4 Zn.nn Set z offset. Positive away from bed, negative closer to bed. - * S5 Reset and disable mesh - * - * The S0 report the points as below - * - * +----> X-axis 1-n - * | - * | - * v Y-axis 1-n - * - */ - inline void gcode_G29() { +#if ENABLED(TEMPERATURE_UNITS_SUPPORT) + #include "gcode/units/M149.h" +#endif - static int mbl_probe_index = -1; - #if HAS_SOFTWARE_ENDSTOPS - static bool enable_soft_endstops; - #endif +#if HAS_POWER_SWITCH + #include "gcode/control/M80.h" +#endif - const MeshLevelingState state = (MeshLevelingState)parser.byteval('S', (int8_t)MeshReport); - if (!WITHIN(state, 0, 5)) { - SERIAL_PROTOCOLLNPGM("S out of range (0-5)."); - return; - } +#include "gcode/control/M81.h" - int8_t px, py; +#include "gcode/units/M82_M83.h" - switch (state) { - case MeshReport: - if (leveling_is_valid()) { - SERIAL_PROTOCOLLNPAIR("State: ", leveling_is_active() ? MSG_ON : MSG_OFF); - mbl_mesh_report(); - } - else - SERIAL_PROTOCOLLNPGM("Mesh bed leveling has no data."); - break; +#include "gcode/control/M18_M84.h" - case MeshStart: - mbl.reset(); - mbl_probe_index = 0; - enqueue_and_echo_commands_P(PSTR("G28\nG29 S2")); - break; +#include "gcode/control/M85.h" - case MeshNext: - if (mbl_probe_index < 0) { - SERIAL_PROTOCOLLNPGM("Start mesh probing with \"G29 S1\" first."); - return; - } - // For each G29 S2... - if (mbl_probe_index == 0) { - #if HAS_SOFTWARE_ENDSTOPS - // For the initial G29 S2 save software endstop state - enable_soft_endstops = soft_endstops_enabled; - #endif - } - else { - // For G29 S2 after adjusting Z. - mbl.set_zigzag_z(mbl_probe_index - 1, current_position[Z_AXIS]); - #if HAS_SOFTWARE_ENDSTOPS - soft_endstops_enabled = enable_soft_endstops; - #endif - } - // If there's another point to sample, move there with optional lift. - if (mbl_probe_index < GRID_MAX_POINTS) { - mbl.zigzag(mbl_probe_index, px, py); - _manual_goto_xy(mbl.index_to_xpos[px], mbl.index_to_ypos[py]); - - #if HAS_SOFTWARE_ENDSTOPS - // Disable software endstops to allow manual adjustment - // If G29 is not completed, they will not be re-enabled - soft_endstops_enabled = false; - #endif +/** + * Multi-stepper support for M92, M201, M203 + */ +#if ENABLED(DISTINCT_E_FACTORS) + #define GET_TARGET_EXTRUDER(CMD) if (get_target_extruder_from_command(CMD)) return + #define TARGET_EXTRUDER target_extruder +#else + #define GET_TARGET_EXTRUDER(CMD) NOOP + #define TARGET_EXTRUDER 0 +#endif - mbl_probe_index++; - } - else { - // One last "return to the bed" (as originally coded) at completion - current_position[Z_AXIS] = LOGICAL_Z_POSITION(Z_MIN_POS) + MANUAL_PROBE_HEIGHT; - line_to_current_position(); - stepper.synchronize(); - - // After recording the last point, activate home and activate - mbl_probe_index = -1; - SERIAL_PROTOCOLLNPGM("Mesh probing done."); - BUZZ(100, 659); - BUZZ(100, 698); - mesh_probing_done(); - } - break; +#include "gcode/config/M92.h" - case MeshSet: - if (parser.seenval('X')) { - px = parser.value_int() - 1; - if (!WITHIN(px, 0, GRID_MAX_POINTS_X - 1)) { - SERIAL_PROTOCOLLNPGM("X out of range (1-" STRINGIFY(GRID_MAX_POINTS_X) ")."); - return; - } - } - else { - SERIAL_CHAR('X'); echo_not_entered(); - return; - } - - if (parser.seenval('Y')) { - py = parser.value_int() - 1; - if (!WITHIN(py, 0, GRID_MAX_POINTS_Y - 1)) { - SERIAL_PROTOCOLLNPGM("Y out of range (1-" STRINGIFY(GRID_MAX_POINTS_Y) ")."); - return; - } - } - else { - SERIAL_CHAR('Y'); echo_not_entered(); - return; - } - - if (parser.seenval('Z')) { - mbl.z_values[px][py] = parser.value_linear_units(); - } - else { - SERIAL_CHAR('Z'); echo_not_entered(); - return; - } - break; - - case MeshSetZOffset: - if (parser.seenval('Z')) { - mbl.z_offset = parser.value_linear_units(); - } - else { - SERIAL_CHAR('Z'); echo_not_entered(); - return; - } - break; - - case MeshReset: - reset_bed_level(); - break; - - } // switch(state) - - report_current_position(); - } - -#elif HAS_ABL && DISABLED(AUTO_BED_LEVELING_UBL) - - #if ABL_GRID - #if ENABLED(PROBE_Y_FIRST) - #define PR_OUTER_VAR xCount - #define PR_OUTER_END abl_grid_points_x - #define PR_INNER_VAR yCount - #define PR_INNER_END abl_grid_points_y - #else - #define PR_OUTER_VAR yCount - #define PR_OUTER_END abl_grid_points_y - #define PR_INNER_VAR xCount - #define PR_INNER_END abl_grid_points_x - #endif - #endif - - /** - * G29: Detailed Z probe, probes the bed at 3 or more points. - * Will fail if the printer has not been homed with G28. - * - * Enhanced G29 Auto Bed Leveling Probe Routine - * - * D Dry-Run mode. Just evaluate the bed Topology - Don't apply - * or alter the bed level data. Useful to check the topology - * after a first run of G29. - * - * J Jettison current bed leveling data - * - * V Set the verbose level (0-4). Example: "G29 V3" - * - * Parameters With LINEAR leveling only: - * - * P Set the size of the grid that will be probed (P x P points). - * Example: "G29 P4" - * - * X Set the X size of the grid that will be probed (X x Y points). - * Example: "G29 X7 Y5" - * - * Y Set the Y size of the grid that will be probed (X x Y points). - * - * T Generate a Bed Topology Report. Example: "G29 P5 T" for a detailed report. - * This is useful for manual bed leveling and finding flaws in the bed (to - * assist with part placement). - * Not supported by non-linear delta printer bed leveling. - * - * Parameters With LINEAR and BILINEAR leveling only: - * - * S Set the XY travel speed between probe points (in units/min) - * - * F Set the Front limit of the probing grid - * B Set the Back limit of the probing grid - * L Set the Left limit of the probing grid - * R Set the Right limit of the probing grid - * - * Parameters with DEBUG_LEVELING_FEATURE only: - * - * C Make a totally fake grid with no actual probing. - * For use in testing when no probing is possible. - * - * Parameters with BILINEAR leveling only: - * - * Z Supply an additional Z probe offset - * - * Extra parameters with PROBE_MANUALLY: - * - * To do manual probing simply repeat G29 until the procedure is complete. - * The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort. - * - * Q Query leveling and G29 state - * - * A Abort current leveling procedure - * - * Extra parameters with BILINEAR only: - * - * W Write a mesh point. (If G29 is idle.) - * I X index for mesh point - * J Y index for mesh point - * X X for mesh point, overrides I - * Y Y for mesh point, overrides J - * Z Z for mesh point. Otherwise, raw current Z. - * - * Without PROBE_MANUALLY: - * - * E By default G29 will engage the Z probe, test the bed, then disengage. - * Include "E" to engage/disengage the Z probe for each sample. - * There's no extra effect if you have a fixed Z probe. - * - */ - inline void gcode_G29() { - - // G29 Q is also available if debugging - #if ENABLED(DEBUG_LEVELING_FEATURE) - const bool query = parser.seen('Q'); - const uint8_t old_debug_flags = marlin_debug_flags; - if (query) marlin_debug_flags |= DEBUG_LEVELING; - if (DEBUGGING(LEVELING)) { - DEBUG_POS(">>> gcode_G29", current_position); - log_machine_info(); - } - marlin_debug_flags = old_debug_flags; - #if DISABLED(PROBE_MANUALLY) - if (query) return; - #endif - #endif - - #if ENABLED(PROBE_MANUALLY) - const bool seenA = parser.seen('A'), seenQ = parser.seen('Q'), no_action = seenA || seenQ; - #endif - - #if ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) - const bool faux = parser.boolval('C'); - #elif ENABLED(PROBE_MANUALLY) - const bool faux = no_action; - #else - bool constexpr faux = false; - #endif - - // Don't allow auto-leveling without homing first - if (axis_unhomed_error()) return; - - // Define local vars 'static' for manual probing, 'auto' otherwise - #if ENABLED(PROBE_MANUALLY) - #define ABL_VAR static - #else - #define ABL_VAR - #endif - - ABL_VAR int verbose_level; - ABL_VAR float xProbe, yProbe, measured_z; - ABL_VAR bool dryrun, abl_should_enable; - - #if ENABLED(PROBE_MANUALLY) || ENABLED(AUTO_BED_LEVELING_LINEAR) - ABL_VAR int abl_probe_index; - #endif - - #if HAS_SOFTWARE_ENDSTOPS && ENABLED(PROBE_MANUALLY) - ABL_VAR bool enable_soft_endstops = true; - #endif - - #if ABL_GRID - - #if ENABLED(PROBE_MANUALLY) - ABL_VAR uint8_t PR_OUTER_VAR; - ABL_VAR int8_t PR_INNER_VAR; - #endif - - ABL_VAR int left_probe_bed_position, right_probe_bed_position, front_probe_bed_position, back_probe_bed_position; - ABL_VAR float xGridSpacing = 0, yGridSpacing = 0; - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - ABL_VAR uint8_t abl_grid_points_x = GRID_MAX_POINTS_X, - abl_grid_points_y = GRID_MAX_POINTS_Y; - ABL_VAR bool do_topography_map; - #else // Bilinear - uint8_t constexpr abl_grid_points_x = GRID_MAX_POINTS_X, - abl_grid_points_y = GRID_MAX_POINTS_Y; - #endif - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(PROBE_MANUALLY) - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - ABL_VAR int abl2; - #else // Bilinear - int constexpr abl2 = GRID_MAX_POINTS; - #endif - #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) - - int constexpr abl2 = 3; - - // Probe at 3 arbitrary points - ABL_VAR vector_3 points[3] = { - vector_3(ABL_PROBE_PT_1_X, ABL_PROBE_PT_1_Y, 0), - vector_3(ABL_PROBE_PT_2_X, ABL_PROBE_PT_2_Y, 0), - vector_3(ABL_PROBE_PT_3_X, ABL_PROBE_PT_3_Y, 0) - }; - - #endif // AUTO_BED_LEVELING_3POINT - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - struct linear_fit_data lsf_results; - incremental_LSF_reset(&lsf_results); - #endif - - /** - * On the initial G29 fetch command parameters. - */ - if (!g29_in_progress) { - - #if ENABLED(PROBE_MANUALLY) || ENABLED(AUTO_BED_LEVELING_LINEAR) - abl_probe_index = -1; - #endif - - abl_should_enable = leveling_is_active(); - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - if (parser.seen('W')) { - if (!leveling_is_valid()) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM("No bilinear grid"); - return; - } - - const float z = parser.floatval('Z', RAW_CURRENT_POSITION(Z)); - if (!WITHIN(z, -10, 10)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM("Bad Z value"); - return; - } - - const float x = parser.floatval('X', NAN), - y = parser.floatval('Y', NAN); - int8_t i = parser.byteval('I', -1), - j = parser.byteval('J', -1); - - if (!isnan(x) && !isnan(y)) { - // Get nearest i / j from x / y - i = (x - LOGICAL_X_POSITION(bilinear_start[X_AXIS]) + 0.5 * xGridSpacing) / xGridSpacing; - j = (y - LOGICAL_Y_POSITION(bilinear_start[Y_AXIS]) + 0.5 * yGridSpacing) / yGridSpacing; - i = constrain(i, 0, GRID_MAX_POINTS_X - 1); - j = constrain(j, 0, GRID_MAX_POINTS_Y - 1); - } - if (WITHIN(i, 0, GRID_MAX_POINTS_X - 1) && WITHIN(j, 0, GRID_MAX_POINTS_Y)) { - set_bed_leveling_enabled(false); - z_values[i][j] = z; - #if ENABLED(ABL_BILINEAR_SUBDIVISION) - bed_level_virt_interpolate(); - #endif - set_bed_leveling_enabled(abl_should_enable); - } - return; - } // parser.seen('W') - - #endif - - #if HAS_LEVELING - - // Jettison bed leveling data - if (parser.seen('J')) { - reset_bed_level(); - return; - } - - #endif - - verbose_level = parser.intval('V'); - if (!WITHIN(verbose_level, 0, 4)) { - SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-4)."); - return; - } - - dryrun = parser.boolval('D') - #if ENABLED(PROBE_MANUALLY) - || no_action - #endif - ; - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - - do_topography_map = verbose_level > 2 || parser.boolval('T'); - - // X and Y specify points in each direction, overriding the default - // These values may be saved with the completed mesh - abl_grid_points_x = parser.intval('X', GRID_MAX_POINTS_X); - abl_grid_points_y = parser.intval('Y', GRID_MAX_POINTS_Y); - if (parser.seenval('P')) abl_grid_points_x = abl_grid_points_y = parser.value_int(); - - if (abl_grid_points_x < 2 || abl_grid_points_y < 2) { - SERIAL_PROTOCOLLNPGM("?Number of probe points is implausible (2 minimum)."); - return; - } - - abl2 = abl_grid_points_x * abl_grid_points_y; - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - zoffset = parser.linearval('Z'); - - #endif - - #if ABL_GRID - - xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_SPEED)); - - left_probe_bed_position = (int)parser.linearval('L', LOGICAL_X_POSITION(LEFT_PROBE_BED_POSITION)); - right_probe_bed_position = (int)parser.linearval('R', LOGICAL_X_POSITION(RIGHT_PROBE_BED_POSITION)); - front_probe_bed_position = (int)parser.linearval('F', LOGICAL_Y_POSITION(FRONT_PROBE_BED_POSITION)); - back_probe_bed_position = (int)parser.linearval('B', LOGICAL_Y_POSITION(BACK_PROBE_BED_POSITION)); - - const bool left_out_l = left_probe_bed_position < LOGICAL_X_POSITION(MIN_PROBE_X), - left_out = left_out_l || left_probe_bed_position > right_probe_bed_position - (MIN_PROBE_EDGE), - right_out_r = right_probe_bed_position > LOGICAL_X_POSITION(MAX_PROBE_X), - right_out = right_out_r || right_probe_bed_position < left_probe_bed_position + MIN_PROBE_EDGE, - front_out_f = front_probe_bed_position < LOGICAL_Y_POSITION(MIN_PROBE_Y), - front_out = front_out_f || front_probe_bed_position > back_probe_bed_position - (MIN_PROBE_EDGE), - back_out_b = back_probe_bed_position > LOGICAL_Y_POSITION(MAX_PROBE_Y), - back_out = back_out_b || back_probe_bed_position < front_probe_bed_position + MIN_PROBE_EDGE; - - if (left_out || right_out || front_out || back_out) { - if (left_out) { - out_of_range_error(PSTR("(L)eft")); - left_probe_bed_position = left_out_l ? LOGICAL_X_POSITION(MIN_PROBE_X) : right_probe_bed_position - (MIN_PROBE_EDGE); - } - if (right_out) { - out_of_range_error(PSTR("(R)ight")); - right_probe_bed_position = right_out_r ? LOGICAL_Y_POSITION(MAX_PROBE_X) : left_probe_bed_position + MIN_PROBE_EDGE; - } - if (front_out) { - out_of_range_error(PSTR("(F)ront")); - front_probe_bed_position = front_out_f ? LOGICAL_Y_POSITION(MIN_PROBE_Y) : back_probe_bed_position - (MIN_PROBE_EDGE); - } - if (back_out) { - out_of_range_error(PSTR("(B)ack")); - back_probe_bed_position = back_out_b ? LOGICAL_Y_POSITION(MAX_PROBE_Y) : front_probe_bed_position + MIN_PROBE_EDGE; - } - return; - } - - // probe at the points of a lattice grid - xGridSpacing = (right_probe_bed_position - left_probe_bed_position) / (abl_grid_points_x - 1); - yGridSpacing = (back_probe_bed_position - front_probe_bed_position) / (abl_grid_points_y - 1); - - #endif // ABL_GRID - - if (verbose_level > 0) { - SERIAL_PROTOCOLLNPGM("G29 Auto Bed Leveling"); - if (dryrun) SERIAL_PROTOCOLLNPGM("Running in DRY-RUN mode"); - } - - stepper.synchronize(); - - // Disable auto bed leveling during G29 - planner.abl_enabled = false; - - if (!dryrun) { - // Re-orient the current position without leveling - // based on where the steppers are positioned. - set_current_from_steppers_for_axis(ALL_AXES); - - // Sync the planner to where the steppers stopped - SYNC_PLAN_POSITION_KINEMATIC(); - } - - #if HAS_BED_PROBE - // Deploy the probe. Probe will raise if needed. - if (DEPLOY_PROBE()) { - planner.abl_enabled = abl_should_enable; - return; - } - #endif - - if (!faux) setup_for_endstop_or_probe_move(); - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - #if ENABLED(PROBE_MANUALLY) - if (!no_action) - #endif - if ( xGridSpacing != bilinear_grid_spacing[X_AXIS] - || yGridSpacing != bilinear_grid_spacing[Y_AXIS] - || left_probe_bed_position != LOGICAL_X_POSITION(bilinear_start[X_AXIS]) - || front_probe_bed_position != LOGICAL_Y_POSITION(bilinear_start[Y_AXIS]) - ) { - if (dryrun) { - // Before reset bed level, re-enable to correct the position - planner.abl_enabled = abl_should_enable; - } - // Reset grid to 0.0 or "not probed". (Also disables ABL) - reset_bed_level(); - - // Initialize a grid with the given dimensions - bilinear_grid_spacing[X_AXIS] = xGridSpacing; - bilinear_grid_spacing[Y_AXIS] = yGridSpacing; - bilinear_start[X_AXIS] = RAW_X_POSITION(left_probe_bed_position); - bilinear_start[Y_AXIS] = RAW_Y_POSITION(front_probe_bed_position); - - // Can't re-enable (on error) until the new grid is written - abl_should_enable = false; - } - - #endif // AUTO_BED_LEVELING_BILINEAR - - #if ENABLED(AUTO_BED_LEVELING_3POINT) - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("> 3-point Leveling"); - #endif - - // Probe at 3 arbitrary points - points[0].z = points[1].z = points[2].z = 0; - - #endif // AUTO_BED_LEVELING_3POINT - - } // !g29_in_progress - - #if ENABLED(PROBE_MANUALLY) - - // For manual probing, get the next index to probe now. - // On the first probe this will be incremented to 0. - if (!no_action) { - ++abl_probe_index; - g29_in_progress = true; - } - - // Abort current G29 procedure, go back to idle state - if (seenA && g29_in_progress) { - SERIAL_PROTOCOLLNPGM("Manual G29 aborted"); - #if HAS_SOFTWARE_ENDSTOPS - soft_endstops_enabled = enable_soft_endstops; - #endif - planner.abl_enabled = abl_should_enable; - g29_in_progress = false; - #if ENABLED(LCD_BED_LEVELING) - lcd_wait_for_move = false; - #endif - } - - // Query G29 status - if (verbose_level || seenQ) { - SERIAL_PROTOCOLPGM("Manual G29 "); - if (g29_in_progress) { - SERIAL_PROTOCOLPAIR("point ", min(abl_probe_index + 1, abl2)); - SERIAL_PROTOCOLLNPAIR(" of ", abl2); - } - else - SERIAL_PROTOCOLLNPGM("idle"); - } - - if (no_action) return; - - if (abl_probe_index == 0) { - // For the initial G29 save software endstop state - #if HAS_SOFTWARE_ENDSTOPS - enable_soft_endstops = soft_endstops_enabled; - #endif - } - else { - // For G29 after adjusting Z. - // Save the previous Z before going to the next point - measured_z = current_position[Z_AXIS]; - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - - mean += measured_z; - eqnBVector[abl_probe_index] = measured_z; - eqnAMatrix[abl_probe_index + 0 * abl2] = xProbe; - eqnAMatrix[abl_probe_index + 1 * abl2] = yProbe; - eqnAMatrix[abl_probe_index + 2 * abl2] = 1; - - incremental_LSF(&lsf_results, xProbe, yProbe, measured_z); - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - z_values[xCount][yCount] = measured_z + zoffset; - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_PROTOCOLPAIR("Save X", xCount); - SERIAL_PROTOCOLPAIR(" Y", yCount); - SERIAL_PROTOCOLLNPAIR(" Z", measured_z + zoffset); - } - #endif - - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - - points[abl_probe_index].z = measured_z; - - #endif - } - - // - // If there's another point to sample, move there with optional lift. - // - - #if ABL_GRID - - // Skip any unreachable points - while (abl_probe_index < abl2) { - - // Set xCount, yCount based on abl_probe_index, with zig-zag - PR_OUTER_VAR = abl_probe_index / PR_INNER_END; - PR_INNER_VAR = abl_probe_index - (PR_OUTER_VAR * PR_INNER_END); - - // Probe in reverse order for every other row/column - bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_END) & 1); - - if (zig) PR_INNER_VAR = (PR_INNER_END - 1) - PR_INNER_VAR; - - const float xBase = xCount * xGridSpacing + left_probe_bed_position, - yBase = yCount * yGridSpacing + front_probe_bed_position; - - xProbe = FLOOR(xBase + (xBase < 0 ? 0 : 0.5)); - yProbe = FLOOR(yBase + (yBase < 0 ? 0 : 0.5)); - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - indexIntoAB[xCount][yCount] = abl_probe_index; - #endif - - // Keep looping till a reachable point is found - if (position_is_reachable_xy(xProbe, yProbe)) break; - ++abl_probe_index; - } - - // Is there a next point to move to? - if (abl_probe_index < abl2) { - _manual_goto_xy(xProbe, yProbe); // Can be used here too! - #if HAS_SOFTWARE_ENDSTOPS - // Disable software endstops to allow manual adjustment - // If G29 is not completed, they will not be re-enabled - soft_endstops_enabled = false; - #endif - return; - } - else { - - // Leveling done! Fall through to G29 finishing code below - - SERIAL_PROTOCOLLNPGM("Grid probing done."); - - // Re-enable software endstops, if needed - #if HAS_SOFTWARE_ENDSTOPS - soft_endstops_enabled = enable_soft_endstops; - #endif - } - - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - - // Probe at 3 arbitrary points - if (abl_probe_index < 3) { - xProbe = LOGICAL_X_POSITION(points[abl_probe_index].x); - yProbe = LOGICAL_Y_POSITION(points[abl_probe_index].y); - #if HAS_SOFTWARE_ENDSTOPS - // Disable software endstops to allow manual adjustment - // If G29 is not completed, they will not be re-enabled - soft_endstops_enabled = false; - #endif - return; - } - else { - - SERIAL_PROTOCOLLNPGM("3-point probing done."); - - // Re-enable software endstops, if needed - #if HAS_SOFTWARE_ENDSTOPS - soft_endstops_enabled = enable_soft_endstops; - #endif - - if (!dryrun) { - vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); - if (planeNormal.z < 0) { - planeNormal.x *= -1; - planeNormal.y *= -1; - planeNormal.z *= -1; - } - planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); - - // Can't re-enable (on error) until the new grid is written - abl_should_enable = false; - } - - } - - #endif // AUTO_BED_LEVELING_3POINT - - #else // !PROBE_MANUALLY - { - const bool stow_probe_after_each = parser.boolval('E'); - - #if ABL_GRID - - bool zig = PR_OUTER_END & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION - - // Outer loop is Y with PROBE_Y_FIRST disabled - for (uint8_t PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_END && !isnan(measured_z); PR_OUTER_VAR++) { - - int8_t inStart, inStop, inInc; - - if (zig) { // away from origin - inStart = 0; - inStop = PR_INNER_END; - inInc = 1; - } - else { // towards origin - inStart = PR_INNER_END - 1; - inStop = -1; - inInc = -1; - } - - zig ^= true; // zag - - // Inner loop is Y with PROBE_Y_FIRST enabled - for (int8_t PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc) { - - float xBase = left_probe_bed_position + xGridSpacing * xCount, - yBase = front_probe_bed_position + yGridSpacing * yCount; - - xProbe = FLOOR(xBase + (xBase < 0 ? 0 : 0.5)); - yProbe = FLOOR(yBase + (yBase < 0 ? 0 : 0.5)); - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - indexIntoAB[xCount][yCount] = ++abl_probe_index; // 0... - #endif - - #if IS_KINEMATIC - // Avoid probing outside the round or hexagonal area - if (!position_is_reachable_by_probe_xy(xProbe, yProbe)) continue; - #endif - - measured_z = faux ? 0.001 * random(-100, 101) : probe_pt(xProbe, yProbe, stow_probe_after_each, verbose_level); - - if (isnan(measured_z)) { - planner.abl_enabled = abl_should_enable; - break; - } - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - - mean += measured_z; - eqnBVector[abl_probe_index] = measured_z; - eqnAMatrix[abl_probe_index + 0 * abl2] = xProbe; - eqnAMatrix[abl_probe_index + 1 * abl2] = yProbe; - eqnAMatrix[abl_probe_index + 2 * abl2] = 1; - - incremental_LSF(&lsf_results, xProbe, yProbe, measured_z); - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - z_values[xCount][yCount] = measured_z + zoffset; - - #endif - - abl_should_enable = false; - idle(); - - } // inner - } // outer - - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - - // Probe at 3 arbitrary points - - for (uint8_t i = 0; i < 3; ++i) { - // Retain the last probe position - xProbe = LOGICAL_X_POSITION(points[i].x); - yProbe = LOGICAL_Y_POSITION(points[i].y); - measured_z = faux ? 0.001 * random(-100, 101) : probe_pt(xProbe, yProbe, stow_probe_after_each, verbose_level); - if (isnan(measured_z)) { - planner.abl_enabled = abl_should_enable; - break; - } - points[i].z = measured_z; - } - - if (!dryrun && !isnan(measured_z)) { - vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); - if (planeNormal.z < 0) { - planeNormal.x *= -1; - planeNormal.y *= -1; - planeNormal.z *= -1; - } - planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); - - // Can't re-enable (on error) until the new grid is written - abl_should_enable = false; - } - - #endif // AUTO_BED_LEVELING_3POINT - - // Raise to _Z_CLEARANCE_DEPLOY_PROBE. Stow the probe. - if (STOW_PROBE()) { - planner.abl_enabled = abl_should_enable; - measured_z = NAN; - } - } - #endif // !PROBE_MANUALLY - - // - // G29 Finishing Code - // - // Unless this is a dry run, auto bed leveling will - // definitely be enabled after this point. - // - // If code above wants to continue leveling, it should - // return or loop before this point. - // - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position); - #endif - - #if ENABLED(PROBE_MANUALLY) - g29_in_progress = false; - #if ENABLED(LCD_BED_LEVELING) - lcd_wait_for_move = false; - #endif - #endif - - // Calculate leveling, print reports, correct the position - if (!isnan(measured_z)) { - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - if (!dryrun) extrapolate_unprobed_bed_level(); - print_bilinear_leveling_grid(); - - refresh_bed_level(); - - #if ENABLED(ABL_BILINEAR_SUBDIVISION) - print_bilinear_leveling_grid_virt(); - #endif - - #elif ENABLED(AUTO_BED_LEVELING_LINEAR) - - // For LINEAR leveling calculate matrix, print reports, correct the position - - /** - * solve the plane equation ax + by + d = z - * A is the matrix with rows [x y 1] for all the probed points - * B is the vector of the Z positions - * the normal vector to the plane is formed by the coefficients of the - * plane equation in the standard form, which is Vx*x+Vy*y+Vz*z+d = 0 - * so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z - */ - float plane_equation_coefficients[3]; - - finish_incremental_LSF(&lsf_results); - plane_equation_coefficients[0] = -lsf_results.A; // We should be able to eliminate the '-' on these three lines and down below - plane_equation_coefficients[1] = -lsf_results.B; // but that is not yet tested. - plane_equation_coefficients[2] = -lsf_results.D; - - mean /= abl2; - - if (verbose_level) { - SERIAL_PROTOCOLPGM("Eqn coefficients: a: "); - SERIAL_PROTOCOL_F(plane_equation_coefficients[0], 8); - SERIAL_PROTOCOLPGM(" b: "); - SERIAL_PROTOCOL_F(plane_equation_coefficients[1], 8); - SERIAL_PROTOCOLPGM(" d: "); - SERIAL_PROTOCOL_F(plane_equation_coefficients[2], 8); - SERIAL_EOL(); - if (verbose_level > 2) { - SERIAL_PROTOCOLPGM("Mean of sampled points: "); - SERIAL_PROTOCOL_F(mean, 8); - SERIAL_EOL(); - } - } - - // Create the matrix but don't correct the position yet - if (!dryrun) - planner.bed_level_matrix = matrix_3x3::create_look_at( - vector_3(-plane_equation_coefficients[0], -plane_equation_coefficients[1], 1) // We can eliminate the '-' here and up above - ); - - // Show the Topography map if enabled - if (do_topography_map) { - - SERIAL_PROTOCOLLNPGM("\nBed Height Topography:\n" - " +--- BACK --+\n" - " | |\n" - " L | (+) | R\n" - " E | | I\n" - " F | (-) N (+) | G\n" - " T | | H\n" - " | (-) | T\n" - " | |\n" - " O-- FRONT --+\n" - " (0,0)"); - - float min_diff = 999; - - for (int8_t yy = abl_grid_points_y - 1; yy >= 0; yy--) { - for (uint8_t xx = 0; xx < abl_grid_points_x; xx++) { - int ind = indexIntoAB[xx][yy]; - float diff = eqnBVector[ind] - mean, - x_tmp = eqnAMatrix[ind + 0 * abl2], - y_tmp = eqnAMatrix[ind + 1 * abl2], - z_tmp = 0; - - apply_rotation_xyz(planner.bed_level_matrix, x_tmp, y_tmp, z_tmp); - - NOMORE(min_diff, eqnBVector[ind] - z_tmp); - - if (diff >= 0.0) - SERIAL_PROTOCOLPGM(" +"); // Include + for column alignment - else - SERIAL_PROTOCOLCHAR(' '); - SERIAL_PROTOCOL_F(diff, 5); - } // xx - SERIAL_EOL(); - } // yy - SERIAL_EOL(); - - if (verbose_level > 3) { - SERIAL_PROTOCOLLNPGM("\nCorrected Bed Height vs. Bed Topology:"); - - for (int8_t yy = abl_grid_points_y - 1; yy >= 0; yy--) { - for (uint8_t xx = 0; xx < abl_grid_points_x; xx++) { - int ind = indexIntoAB[xx][yy]; - float x_tmp = eqnAMatrix[ind + 0 * abl2], - y_tmp = eqnAMatrix[ind + 1 * abl2], - z_tmp = 0; - - apply_rotation_xyz(planner.bed_level_matrix, x_tmp, y_tmp, z_tmp); - - float diff = eqnBVector[ind] - z_tmp - min_diff; - if (diff >= 0.0) - SERIAL_PROTOCOLPGM(" +"); - // Include + for column alignment - else - SERIAL_PROTOCOLCHAR(' '); - SERIAL_PROTOCOL_F(diff, 5); - } // xx - SERIAL_EOL(); - } // yy - SERIAL_EOL(); - } - } //do_topography_map - - #endif // AUTO_BED_LEVELING_LINEAR - - #if ABL_PLANAR - - // For LINEAR and 3POINT leveling correct the current position - - if (verbose_level > 0) - planner.bed_level_matrix.debug(PSTR("\n\nBed Level Correction Matrix:")); - - if (!dryrun) { - // - // Correct the current XYZ position based on the tilted plane. - // - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position); - #endif - - float converted[XYZ]; - COPY(converted, current_position); - - planner.abl_enabled = true; - planner.unapply_leveling(converted); // use conversion machinery - planner.abl_enabled = false; - - // Use the last measured distance to the bed, if possible - if ( NEAR(current_position[X_AXIS], xProbe - (X_PROBE_OFFSET_FROM_EXTRUDER)) - && NEAR(current_position[Y_AXIS], yProbe - (Y_PROBE_OFFSET_FROM_EXTRUDER)) - ) { - const float simple_z = current_position[Z_AXIS] - measured_z; - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOPAIR("Z from Probe:", simple_z); - SERIAL_ECHOPAIR(" Matrix:", converted[Z_AXIS]); - SERIAL_ECHOLNPAIR(" Discrepancy:", simple_z - converted[Z_AXIS]); - } - #endif - converted[Z_AXIS] = simple_z; - } - - // The rotated XY and corrected Z are now current_position - COPY(current_position, converted); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position); - #endif - } - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - if (!dryrun) { - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("G29 uncorrected Z:", current_position[Z_AXIS]); - #endif - - // Unapply the offset because it is going to be immediately applied - // and cause compensation movement in Z - current_position[Z_AXIS] -= bilinear_z_offset(current_position); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR(" corrected Z:", current_position[Z_AXIS]); - #endif - } - - #endif // ABL_PLANAR - - #ifdef Z_PROBE_END_SCRIPT - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT); - #endif - enqueue_and_echo_commands_P(PSTR(Z_PROBE_END_SCRIPT)); - stepper.synchronize(); - #endif - - // Auto Bed Leveling is complete! Enable if possible. - planner.abl_enabled = dryrun ? abl_should_enable : true; - } // !isnan(measured_z) - - // Restore state after probing - if (!faux) clean_up_after_endstop_or_probe_move(); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< gcode_G29"); - #endif - - report_current_position(); - - KEEPALIVE_STATE(IN_HANDLER); - - if (planner.abl_enabled) - SYNC_PLAN_POSITION_KINEMATIC(); - } - -#endif // HAS_ABL && !AUTO_BED_LEVELING_UBL - -#if HAS_BED_PROBE - - /** - * G30: Do a single Z probe at the current XY - * - * Parameters: - * - * X Probe X position (default current X) - * Y Probe Y position (default current Y) - * S0 Leave the probe deployed - */ - inline void gcode_G30() { - const float xpos = parser.linearval('X', current_position[X_AXIS] + X_PROBE_OFFSET_FROM_EXTRUDER), - ypos = parser.linearval('Y', current_position[Y_AXIS] + Y_PROBE_OFFSET_FROM_EXTRUDER); - - if (!position_is_reachable_by_probe_xy(xpos, ypos)) return; - - // Disable leveling so the planner won't mess with us - #if HAS_LEVELING - set_bed_leveling_enabled(false); - #endif - - setup_for_endstop_or_probe_move(); - - const float measured_z = probe_pt(xpos, ypos, parser.boolval('S', true), 1); - - if (!isnan(measured_z)) { - SERIAL_PROTOCOLPAIR("Bed X: ", FIXFLOAT(xpos)); - SERIAL_PROTOCOLPAIR(" Y: ", FIXFLOAT(ypos)); - SERIAL_PROTOCOLLNPAIR(" Z: ", FIXFLOAT(measured_z)); - } - - clean_up_after_endstop_or_probe_move(); - - report_current_position(); - } - - #if ENABLED(Z_PROBE_SLED) - - /** - * G31: Deploy the Z probe - */ - inline void gcode_G31() { DEPLOY_PROBE(); } - - /** - * G32: Stow the Z probe - */ - inline void gcode_G32() { STOW_PROBE(); } - - #endif // Z_PROBE_SLED - -#endif // HAS_BED_PROBE - -#if PROBE_SELECTED - - #if ENABLED(DELTA_AUTO_CALIBRATION) - /** - * G33 - Delta '1-4-7-point' Auto-Calibration - * Calibrate height, endstops, delta radius, and tower angles. - * - * Parameters: - * - * Pn Number of probe points: - * - * P1 Probe center and set height only. - * P2 Probe center and towers. Set height, endstops, and delta radius. - * P3 Probe all positions: center, towers and opposite towers. Set all. - * P4-P7 Probe all positions at different locations and average them. - * - * T0 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 takes the best result - * - * Vn Verbose level: - * - * V0 Dry-run mode. Report settings and probe results. No calibration. - * V1 Report settings - * V2 Report settings and probe results - * - * E Engage the probe for each point - */ - - void print_signed_float(const char * const prefix, const float &f) { - SERIAL_PROTOCOLPGM(" "); - serialprintPGM(prefix); - SERIAL_PROTOCOLCHAR(':'); - if (f >= 0) SERIAL_CHAR('+'); - SERIAL_PROTOCOL_F(f, 2); - } - - inline void print_G33_settings(const bool end_stops, const bool tower_angles){ // TODO echo these to LCD ??? - SERIAL_PROTOCOLPAIR(".Height:", DELTA_HEIGHT + home_offset[Z_AXIS]); - if (end_stops) { - print_signed_float(PSTR(" Ex"), endstop_adj[A_AXIS]); - print_signed_float(PSTR("Ey"), endstop_adj[B_AXIS]); - print_signed_float(PSTR("Ez"), endstop_adj[C_AXIS]); - SERIAL_PROTOCOLPAIR(" Radius:", delta_radius); - } - SERIAL_EOL(); - if (tower_angles) { - SERIAL_PROTOCOLPGM(".Tower angle : "); - print_signed_float(PSTR("Tx"), delta_tower_angle_trim[A_AXIS]); - print_signed_float(PSTR("Ty"), delta_tower_angle_trim[B_AXIS]); - SERIAL_PROTOCOLLNPGM(" Tz:+0.00"); - } - } - - void G33_cleanup( - #if HOTENDS > 1 - const uint8_t old_tool_index - #endif - ) { - #if ENABLED(DELTA_HOME_TO_SAFE_ZONE) - do_blocking_move_to_z(delta_clip_start_height); - #endif - STOW_PROBE(); - clean_up_after_endstop_or_probe_move(); - #if HOTENDS > 1 - tool_change(old_tool_index, 0, true); - #endif - } - - inline void gcode_G33() { - - const int8_t probe_points = parser.intval('P', DELTA_CALIBRATION_DEFAULT_POINTS); - if (!WITHIN(probe_points, 1, 7)) { - SERIAL_PROTOCOLLNPGM("?(P)oints is implausible (1-7)."); - return; - } - - const int8_t verbose_level = parser.byteval('V', 1); - if (!WITHIN(verbose_level, 0, 2)) { - SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-2)."); - return; - } - - const float calibration_precision = parser.floatval('C'); - if (calibration_precision < 0) { - SERIAL_PROTOCOLLNPGM("?(C)alibration precision is implausible (>0)."); - return; - } - - const int8_t force_iterations = parser.intval('F', 0); - if (!WITHIN(force_iterations, 0, 30)) { - SERIAL_PROTOCOLLNPGM("?(F)orce iteration is implausible (0-30)."); - return; - } - - const bool towers_set = parser.boolval('T', true), - stow_after_each = parser.boolval('E'), - _1p_calibration = probe_points == 1, - _4p_calibration = probe_points == 2, - _4p_towers_points = _4p_calibration && towers_set, - _4p_opposite_points = _4p_calibration && !towers_set, - _7p_calibration = probe_points >= 3, - _7p_half_circle = probe_points == 3, - _7p_double_circle = probe_points == 5, - _7p_triple_circle = probe_points == 6, - _7p_quadruple_circle = probe_points == 7, - _7p_multi_circle = _7p_double_circle || _7p_triple_circle || _7p_quadruple_circle, - _7p_intermed_points = _7p_calibration && !_7p_half_circle; - const static char save_message[] PROGMEM = "Save with M500 and/or copy to Configuration.h"; - const float dx = (X_PROBE_OFFSET_FROM_EXTRUDER), - dy = (Y_PROBE_OFFSET_FROM_EXTRUDER); - int8_t iterations = 0; - float test_precision, - zero_std_dev = (verbose_level ? 999.0 : 0.0), // 0.0 in dry-run mode : forced end - zero_std_dev_old = zero_std_dev, - zero_std_dev_min = zero_std_dev, - e_old[XYZ] = { - endstop_adj[A_AXIS], - endstop_adj[B_AXIS], - endstop_adj[C_AXIS] - }, - dr_old = delta_radius, - zh_old = home_offset[Z_AXIS], - alpha_old = delta_tower_angle_trim[A_AXIS], - beta_old = delta_tower_angle_trim[B_AXIS]; - - if (!_1p_calibration) { // test if the outer radius is reachable - const float circles = (_7p_quadruple_circle ? 1.5 : - _7p_triple_circle ? 1.0 : - _7p_double_circle ? 0.5 : 0), - r = (1 + circles * 0.1) * delta_calibration_radius; - for (uint8_t axis = 1; axis < 13; ++axis) { - const float a = RADIANS(180 + 30 * axis); - if (!position_is_reachable_xy(cos(a) * r, sin(a) * r)) { - SERIAL_PROTOCOLLNPGM("?(M665 B)ed radius is implausible."); - return; - } - } - } - SERIAL_PROTOCOLLNPGM("G33 Auto Calibrate"); - - stepper.synchronize(); - #if HAS_LEVELING - reset_bed_level(); // After calibration bed-level data is no longer valid - #endif - - #if HOTENDS > 1 - const uint8_t old_tool_index = active_extruder; - tool_change(0, 0, true); - #define G33_CLEANUP() G33_cleanup(old_tool_index) - #else - #define G33_CLEANUP() G33_cleanup() - #endif - - setup_for_endstop_or_probe_move(); - endstops.enable(true); - if (!home_delta()) - return; - endstops.not_homing(); - - // print settings - - const char *checkingac = PSTR("Checking... AC"); // TODO: Make translatable string - serialprintPGM(checkingac); - if (verbose_level == 0) SERIAL_PROTOCOLPGM(" (DRY-RUN)"); - SERIAL_EOL(); - lcd_setstatusPGM(checkingac); - - print_G33_settings(!_1p_calibration, _7p_calibration && towers_set); - - #if DISABLED(PROBE_MANUALLY) - const float measured_z = probe_pt(dx, dy, stow_after_each, 1, false); // 1st probe to set height - if (isnan(measured_z)) return G33_CLEANUP(); - home_offset[Z_AXIS] -= measured_z; - #endif - - do { - - float z_at_pt[13] = { 0.0 }; - - test_precision = zero_std_dev_old != 999.0 ? (zero_std_dev + zero_std_dev_old) / 2 : zero_std_dev; - - iterations++; - - // Probe the points - - if (!_7p_half_circle && !_7p_triple_circle) { // probe the center - #if ENABLED(PROBE_MANUALLY) - z_at_pt[0] += lcd_probe_pt(0, 0); - #else - z_at_pt[0] += probe_pt(dx, dy, stow_after_each, 1, false); - if (isnan(z_at_pt[0])) return G33_CLEANUP(); - #endif - } - if (_7p_calibration) { // probe extra center points - for (int8_t axis = _7p_multi_circle ? 11 : 9; axis > 0; axis -= _7p_multi_circle ? 2 : 4) { - const float a = RADIANS(180 + 30 * axis), r = delta_calibration_radius * 0.1; - #if ENABLED(PROBE_MANUALLY) - z_at_pt[0] += lcd_probe_pt(cos(a) * r, sin(a) * r); - #else - z_at_pt[0] += probe_pt(cos(a) * r + dx, sin(a) * r + dy, stow_after_each, 1); - if (isnan(z_at_pt[0])) return G33_CLEANUP(); - #endif - } - z_at_pt[0] /= float(_7p_double_circle ? 7 : probe_points); - } - if (!_1p_calibration) { // probe the radius - bool zig_zag = true; - const uint8_t start = _4p_opposite_points ? 3 : 1, - step = _4p_calibration ? 4 : _7p_half_circle ? 2 : 1; - for (uint8_t axis = start; axis < 13; axis += step) { - const float zigadd = (zig_zag ? 0.5 : 0.0), - offset_circles = _7p_quadruple_circle ? zigadd + 1.0 : - _7p_triple_circle ? zigadd + 0.5 : - _7p_double_circle ? zigadd : 0; - for (float circles = -offset_circles ; circles <= offset_circles; circles++) { - const float a = RADIANS(180 + 30 * axis), - r = delta_calibration_radius * (1 + circles * (zig_zag ? 0.1 : -0.1)); - #if ENABLED(PROBE_MANUALLY) - z_at_pt[axis] += lcd_probe_pt(cos(a) * r, sin(a) * r); - #else - z_at_pt[axis] += probe_pt(cos(a) * r + dx, sin(a) * r + dy, stow_after_each, 1); - if (isnan(z_at_pt[axis])) return G33_CLEANUP(); - #endif - } - zig_zag = !zig_zag; - z_at_pt[axis] /= (2 * offset_circles + 1); - } - } - if (_7p_intermed_points) // average intermediates to tower and opposites - for (uint8_t axis = 1; axis < 13; axis += 2) - z_at_pt[axis] = (z_at_pt[axis] + (z_at_pt[axis + 1] + z_at_pt[(axis + 10) % 12 + 1]) / 2.0) / 2.0; - - float S1 = z_at_pt[0], - S2 = sq(z_at_pt[0]); - int16_t N = 1; - if (!_1p_calibration) // std dev from zero plane - for (uint8_t axis = (_4p_opposite_points ? 3 : 1); axis < 13; axis += (_4p_calibration ? 4 : 2)) { - S1 += z_at_pt[axis]; - S2 += sq(z_at_pt[axis]); - N++; - } - zero_std_dev_old = zero_std_dev; - zero_std_dev = round(SQRT(S2 / N) * 1000.0) / 1000.0 + 0.00001; - - // Solve matrices - - if ((zero_std_dev < test_precision && zero_std_dev > calibration_precision) || iterations <= force_iterations) { - if (zero_std_dev < zero_std_dev_min) { - COPY(e_old, endstop_adj); - dr_old = delta_radius; - zh_old = home_offset[Z_AXIS]; - alpha_old = delta_tower_angle_trim[A_AXIS]; - beta_old = delta_tower_angle_trim[B_AXIS]; - } - - float e_delta[XYZ] = { 0.0 }, r_delta = 0.0, t_alpha = 0.0, t_beta = 0.0; - const float r_diff = delta_radius - delta_calibration_radius, - h_factor = 1.00 + r_diff * 0.001, //1.02 for r_diff = 20mm - r_factor = -(1.75 + 0.005 * r_diff + 0.001 * sq(r_diff)), //2.25 for r_diff = 20mm - a_factor = 100.0 / delta_calibration_radius; //1.25 for cal_rd = 80mm - - #define ZP(N,I) ((N) * z_at_pt[I]) - #define Z1000(I) ZP(1.00, I) - #define Z1050(I) ZP(h_factor, I) - #define Z0700(I) ZP(h_factor * 2.0 / 3.00, I) - #define Z0350(I) ZP(h_factor / 3.00, I) - #define Z0175(I) ZP(h_factor / 6.00, I) - #define Z2250(I) ZP(r_factor, I) - #define Z0750(I) ZP(r_factor / 3.00, I) - #define Z0375(I) ZP(r_factor / 6.00, I) - #define Z0444(I) ZP(a_factor * 4.0 / 9.0, I) - #define Z0888(I) ZP(a_factor * 8.0 / 9.0, I) - - #if ENABLED(PROBE_MANUALLY) - test_precision = 0.00; // forced end - #endif - - switch (probe_points) { - case 1: - test_precision = 0.00; // forced end - LOOP_XYZ(i) e_delta[i] = Z1000(0); - break; - - case 2: - if (towers_set) { - e_delta[X_AXIS] = Z1050(0) + Z0700(1) - Z0350(5) - Z0350(9); - e_delta[Y_AXIS] = Z1050(0) - Z0350(1) + Z0700(5) - Z0350(9); - e_delta[Z_AXIS] = Z1050(0) - Z0350(1) - Z0350(5) + Z0700(9); - r_delta = Z2250(0) - Z0750(1) - Z0750(5) - Z0750(9); - } - else { - e_delta[X_AXIS] = Z1050(0) - Z0700(7) + Z0350(11) + Z0350(3); - e_delta[Y_AXIS] = Z1050(0) + Z0350(7) - Z0700(11) + Z0350(3); - e_delta[Z_AXIS] = Z1050(0) + Z0350(7) + Z0350(11) - Z0700(3); - r_delta = Z2250(0) - Z0750(7) - Z0750(11) - Z0750(3); - } - break; - - default: - e_delta[X_AXIS] = Z1050(0) + Z0350(1) - Z0175(5) - Z0175(9) - Z0350(7) + Z0175(11) + Z0175(3); - e_delta[Y_AXIS] = Z1050(0) - Z0175(1) + Z0350(5) - Z0175(9) + Z0175(7) - Z0350(11) + Z0175(3); - e_delta[Z_AXIS] = Z1050(0) - Z0175(1) - Z0175(5) + Z0350(9) + Z0175(7) + Z0175(11) - Z0350(3); - r_delta = Z2250(0) - Z0375(1) - Z0375(5) - Z0375(9) - Z0375(7) - Z0375(11) - Z0375(3); - - if (towers_set) { - t_alpha = Z0444(1) - Z0888(5) + Z0444(9) + Z0444(7) - Z0888(11) + Z0444(3); - t_beta = Z0888(1) - Z0444(5) - Z0444(9) + Z0888(7) - Z0444(11) - Z0444(3); - } - break; - } - - LOOP_XYZ(axis) endstop_adj[axis] += e_delta[axis]; - delta_radius += r_delta; - delta_tower_angle_trim[A_AXIS] += t_alpha; - delta_tower_angle_trim[B_AXIS] += t_beta; - - // adjust delta_height and endstops by the max amount - const float z_temp = MAX3(endstop_adj[A_AXIS], endstop_adj[B_AXIS], endstop_adj[C_AXIS]); - home_offset[Z_AXIS] -= z_temp; - LOOP_XYZ(i) endstop_adj[i] -= z_temp; - - recalc_delta_settings(delta_radius, delta_diagonal_rod); - } - else if (zero_std_dev >= test_precision) { // step one back - COPY(endstop_adj, e_old); - delta_radius = dr_old; - home_offset[Z_AXIS] = zh_old; - delta_tower_angle_trim[A_AXIS] = alpha_old; - delta_tower_angle_trim[B_AXIS] = beta_old; - - recalc_delta_settings(delta_radius, delta_diagonal_rod); - } - NOMORE(zero_std_dev_min, zero_std_dev); - - // print report - - if (verbose_level != 1) { - SERIAL_PROTOCOLPGM(". "); - print_signed_float(PSTR("c"), z_at_pt[0]); - if (_4p_towers_points || _7p_calibration) { - print_signed_float(PSTR(" x"), z_at_pt[1]); - print_signed_float(PSTR(" y"), z_at_pt[5]); - print_signed_float(PSTR(" z"), z_at_pt[9]); - } - if (!_4p_opposite_points) SERIAL_EOL(); - if ((_4p_opposite_points) || _7p_calibration) { - if (_7p_calibration) { - SERIAL_CHAR('.'); - SERIAL_PROTOCOL_SP(13); - } - print_signed_float(PSTR(" yz"), z_at_pt[7]); - print_signed_float(PSTR("zx"), z_at_pt[11]); - print_signed_float(PSTR("xy"), z_at_pt[3]); - SERIAL_EOL(); - } - } - if (verbose_level != 0) { // !dry run - if ((zero_std_dev >= test_precision || zero_std_dev <= calibration_precision) && iterations > force_iterations) { // end iterations - SERIAL_PROTOCOLPGM("Calibration OK"); - SERIAL_PROTOCOL_SP(36); - #if DISABLED(PROBE_MANUALLY) - if (zero_std_dev >= test_precision && !_1p_calibration) - SERIAL_PROTOCOLPGM("rolling back."); - else - #endif - { - SERIAL_PROTOCOLPGM("std dev:"); - SERIAL_PROTOCOL_F(zero_std_dev_min, 3); - } - SERIAL_EOL(); - char mess[21]; - sprintf_P(mess, PSTR("Calibration sd:")); - if (zero_std_dev_min < 1) - sprintf_P(&mess[15], PSTR("0.%03i"), (int)round(zero_std_dev_min * 1000.0)); - else - sprintf_P(&mess[15], PSTR("%03i.x"), (int)round(zero_std_dev_min)); - lcd_setstatus(mess); - print_G33_settings(!_1p_calibration, _7p_calibration && towers_set); - serialprintPGM(save_message); - SERIAL_EOL(); - } - else { // !end iterations - char mess[15]; - if (iterations < 31) - sprintf_P(mess, PSTR("Iteration : %02i"), (int)iterations); - else - sprintf_P(mess, PSTR("No convergence")); - SERIAL_PROTOCOL(mess); - SERIAL_PROTOCOL_SP(36); - SERIAL_PROTOCOLPGM("std dev:"); - SERIAL_PROTOCOL_F(zero_std_dev, 3); - SERIAL_EOL(); - lcd_setstatus(mess); - print_G33_settings(!_1p_calibration, _7p_calibration && towers_set); - } - } - else { // dry run - const char *enddryrun = PSTR("End DRY-RUN"); - serialprintPGM(enddryrun); - SERIAL_PROTOCOL_SP(39); - SERIAL_PROTOCOLPGM("std dev:"); - SERIAL_PROTOCOL_F(zero_std_dev, 3); - SERIAL_EOL(); - - char mess[21]; - sprintf_P(mess, enddryrun); - sprintf_P(&mess[11], PSTR(" sd:")); - if (zero_std_dev < 1) - sprintf_P(&mess[15], PSTR("0.%03i"), (int)round(zero_std_dev * 1000.0)); - else - sprintf_P(&mess[15], PSTR("%03i.x"), (int)round(zero_std_dev)); - lcd_setstatus(mess); - } - - endstops.enable(true); - home_delta(); - endstops.not_homing(); - - } - while ((zero_std_dev < test_precision && zero_std_dev > calibration_precision && iterations < 31) || iterations <= force_iterations); - - G33_CLEANUP(); - } - - #endif // DELTA_AUTO_CALIBRATION - -#endif // PROBE_SELECTED - -#if ENABLED(G38_PROBE_TARGET) - - static bool G38_run_probe() { - - bool G38_pass_fail = false; - - #if ENABLED(PROBE_DOUBLE_TOUCH) - // Get direction of move and retract - float retract_mm[XYZ]; - LOOP_XYZ(i) { - float dist = destination[i] - current_position[i]; - retract_mm[i] = FABS(dist) < G38_MINIMUM_MOVE ? 0 : home_bump_mm((AxisEnum)i) * (dist > 0 ? -1 : 1); - } - #endif - - stepper.synchronize(); // wait until the machine is idle - - // Move until destination reached or target hit - endstops.enable(true); - G38_move = true; - G38_endstop_hit = false; - prepare_move_to_destination(); - stepper.synchronize(); - G38_move = false; - - endstops.hit_on_purpose(); - set_current_from_steppers_for_axis(ALL_AXES); - SYNC_PLAN_POSITION_KINEMATIC(); - - if (G38_endstop_hit) { - - G38_pass_fail = true; - - #if ENABLED(PROBE_DOUBLE_TOUCH) - // Move away by the retract distance - set_destination_to_current(); - LOOP_XYZ(i) destination[i] += retract_mm[i]; - endstops.enable(false); - prepare_move_to_destination(); - stepper.synchronize(); - - feedrate_mm_s /= 4; - - // Bump the target more slowly - LOOP_XYZ(i) destination[i] -= retract_mm[i] * 2; - - endstops.enable(true); - G38_move = true; - prepare_move_to_destination(); - stepper.synchronize(); - G38_move = false; - - set_current_from_steppers_for_axis(ALL_AXES); - SYNC_PLAN_POSITION_KINEMATIC(); - #endif - } - - endstops.hit_on_purpose(); - endstops.not_homing(); - return G38_pass_fail; - } - - /** - * G38.2 - probe toward workpiece, stop on contact, signal error if failure - * G38.3 - probe toward workpiece, stop on contact - * - * Like G28 except uses Z min probe for all axes - */ - inline void gcode_G38(bool is_38_2) { - // Get X Y Z E F - gcode_get_destination(); - - setup_for_endstop_or_probe_move(); - - // If any axis has enough movement, do the move - LOOP_XYZ(i) - if (FABS(destination[i] - current_position[i]) >= G38_MINIMUM_MOVE) { - if (!parser.seenval('F')) feedrate_mm_s = homing_feedrate((AxisEnum)i); - // If G38.2 fails throw an error - if (!G38_run_probe() && is_38_2) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM("Failed to reach target"); - } - break; - } - - clean_up_after_endstop_or_probe_move(); - } - -#endif // G38_PROBE_TARGET - -#if ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(MESH_BED_LEVELING) - - /** - * G42: Move X & Y axes to mesh coordinates (I & J) - */ - inline void gcode_G42() { - if (IsRunning()) { - const bool hasI = parser.seenval('I'); - const int8_t ix = hasI ? parser.value_int() : 0; - const bool hasJ = parser.seenval('J'); - const int8_t iy = hasJ ? parser.value_int() : 0; - - if ((hasI && !WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) || (hasJ && !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1))) { - SERIAL_ECHOLNPGM(MSG_ERR_MESH_XY); - return; - } - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - #define _GET_MESH_X(I) bilinear_start[X_AXIS] + I * bilinear_grid_spacing[X_AXIS] - #define _GET_MESH_Y(J) bilinear_start[Y_AXIS] + J * bilinear_grid_spacing[Y_AXIS] - #elif ENABLED(AUTO_BED_LEVELING_UBL) - #define _GET_MESH_X(I) ubl.mesh_index_to_xpos(I) - #define _GET_MESH_Y(J) ubl.mesh_index_to_ypos(J) - #elif ENABLED(MESH_BED_LEVELING) - #define _GET_MESH_X(I) mbl.index_to_xpos[I] - #define _GET_MESH_Y(J) mbl.index_to_ypos[J] - #endif - - set_destination_to_current(); - if (hasI) destination[X_AXIS] = LOGICAL_X_POSITION(_GET_MESH_X(ix)); - if (hasJ) destination[Y_AXIS] = LOGICAL_Y_POSITION(_GET_MESH_Y(iy)); - if (parser.boolval('P')) { - if (hasI) destination[X_AXIS] -= X_PROBE_OFFSET_FROM_EXTRUDER; - if (hasJ) destination[Y_AXIS] -= Y_PROBE_OFFSET_FROM_EXTRUDER; - } - - const float fval = parser.linearval('F'); - if (fval > 0.0) feedrate_mm_s = MMM_TO_MMS(fval); - - // SCARA kinematic has "safe" XY raw moves - #if IS_SCARA - prepare_uninterpolated_move_to_destination(); - #else - prepare_move_to_destination(); - #endif - } - } - -#endif // AUTO_BED_LEVELING_UBL - -/** - * G92: Set current position to given X Y Z E - */ -inline void gcode_G92() { - bool didXYZ = false, - didE = parser.seenval('E'); - - if (!didE) stepper.synchronize(); - - LOOP_XYZE(i) { - if (parser.seenval(axis_codes[i])) { - #if IS_SCARA - current_position[i] = parser.value_axis_units((AxisEnum)i); - if (i != E_AXIS) didXYZ = true; - #else - #if HAS_POSITION_SHIFT - const float p = current_position[i]; - #endif - const float v = parser.value_axis_units((AxisEnum)i); - - current_position[i] = v; - - if (i != E_AXIS) { - didXYZ = true; - #if HAS_POSITION_SHIFT - position_shift[i] += v - p; // Offset the coordinate space - update_software_endstops((AxisEnum)i); - - #if ENABLED(I2C_POSITION_ENCODERS) - I2CPEM.encoders[I2CPEM.idx_from_axis((AxisEnum)i)].set_axis_offset(position_shift[i]); - #endif - - #endif - } - #endif - } - } - if (didXYZ) - SYNC_PLAN_POSITION_KINEMATIC(); - else if (didE) - sync_plan_position_e(); - - report_current_position(); -} - -#if HAS_RESUME_CONTINUE - - /** - * M0: Unconditional stop - Wait for user button press on LCD - * M1: Conditional stop - Wait for user button press on LCD - */ - inline void gcode_M0_M1() { - const char * const args = parser.string_arg; - - millis_t ms = 0; - bool hasP = false, hasS = false; - if (parser.seenval('P')) { - ms = parser.value_millis(); // milliseconds to wait - hasP = ms > 0; - } - if (parser.seenval('S')) { - ms = parser.value_millis_from_seconds(); // seconds to wait - hasS = ms > 0; - } - - #if ENABLED(ULTIPANEL) - - if (!hasP && !hasS && args && *args) - lcd_setstatus(args, true); - else { - LCD_MESSAGEPGM(MSG_USERWAIT); - #if ENABLED(LCD_PROGRESS_BAR) && PROGRESS_MSG_EXPIRE > 0 - dontExpireStatus(); - #endif - } - - #else - - if (!hasP && !hasS && args && *args) { - SERIAL_ECHO_START(); - SERIAL_ECHOLN(args); - } - - #endif - - KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = true; - - stepper.synchronize(); - refresh_cmd_timeout(); - - if (ms > 0) { - ms += previous_cmd_ms; // wait until this time for a click - while (PENDING(millis(), ms) && wait_for_user) idle(); - } - else { - #if ENABLED(ULTIPANEL) - if (lcd_detected()) { - while (wait_for_user) idle(); - IS_SD_PRINTING ? LCD_MESSAGEPGM(MSG_RESUMING) : LCD_MESSAGEPGM(WELCOME_MSG); - } - #else - while (wait_for_user) idle(); - #endif - } - - wait_for_user = false; - KEEPALIVE_STATE(IN_HANDLER); - } - -#endif // HAS_RESUME_CONTINUE - -#if ENABLED(SPINDLE_LASER_ENABLE) - /** - * M3: Spindle Clockwise - * M4: Spindle Counter-clockwise - * - * S0 turns off spindle. - * - * If no speed PWM output is defined then M3/M4 just turns it on. - * - * At least 12.8KHz (50Hz * 256) is needed for spindle PWM. - * Hardware PWM is required. ISRs are too slow. - * - * NOTE: WGM for timers 3, 4, and 5 must be either Mode 1 or Mode 5. - * No other settings give a PWM signal that goes from 0 to 5 volts. - * - * The system automatically sets WGM to Mode 1, so no special - * initialization is needed. - * - * WGM bits for timer 2 are automatically set by the system to - * Mode 1. This produces an acceptable 0 to 5 volt signal. - * No special initialization is needed. - * - * NOTE: A minimum PWM frequency of 50 Hz is needed. All prescaler - * factors for timers 2, 3, 4, and 5 are acceptable. - * - * SPINDLE_LASER_ENABLE_PIN needs an external pullup or it may power on - * the spindle/laser during power-up or when connecting to the host - * (usually goes through a reset which sets all I/O pins to tri-state) - * - * PWM duty cycle goes from 0 (off) to 255 (always on). - */ - - // Wait for spindle to come up to speed - inline void delay_for_power_up() { dwell(SPINDLE_LASER_POWERUP_DELAY); } - - // Wait for spindle to stop turning - inline void delay_for_power_down() { dwell(SPINDLE_LASER_POWERDOWN_DELAY); } - - /** - * ocr_val_mode() is used for debugging and to get the points needed to compute the RPM vs ocr_val line - * - * it accepts inputs of 0-255 - */ - - inline void ocr_val_mode() { - uint8_t spindle_laser_power = parser.value_byte(); - WRITE(SPINDLE_LASER_ENABLE_PIN, SPINDLE_LASER_ENABLE_INVERT); // turn spindle on (active low) - if (SPINDLE_LASER_PWM_INVERT) spindle_laser_power = 255 - spindle_laser_power; - analogWrite(SPINDLE_LASER_PWM_PIN, spindle_laser_power); - } - - inline void gcode_M3_M4(bool is_M3) { - - stepper.synchronize(); // wait until previous movement commands (G0/G0/G2/G3) have completed before playing with the spindle - #if SPINDLE_DIR_CHANGE - const bool rotation_dir = (is_M3 && !SPINDLE_INVERT_DIR || !is_M3 && SPINDLE_INVERT_DIR) ? HIGH : LOW; - if (SPINDLE_STOP_ON_DIR_CHANGE \ - && READ(SPINDLE_LASER_ENABLE_PIN) == SPINDLE_LASER_ENABLE_INVERT \ - && READ(SPINDLE_DIR_PIN) != rotation_dir - ) { - WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); // turn spindle off - delay_for_power_down(); - } - WRITE(SPINDLE_DIR_PIN, rotation_dir); - #endif - - /** - * Our final value for ocr_val is an unsigned 8 bit value between 0 and 255 which usually means uint8_t. - * Went to uint16_t because some of the uint8_t calculations would sometimes give 1000 0000 rather than 1111 1111. - * Then needed to AND the uint16_t result with 0x00FF to make sure we only wrote the byte of interest. - */ - #if ENABLED(SPINDLE_LASER_PWM) - if (parser.seen('O')) ocr_val_mode(); - else { - const float spindle_laser_power = parser.floatval('S'); - if (spindle_laser_power == 0) { - WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); // turn spindle off (active low) - delay_for_power_down(); - } - else { - int16_t ocr_val = (spindle_laser_power - (SPEED_POWER_INTERCEPT)) * (1.0 / (SPEED_POWER_SLOPE)); // convert RPM to PWM duty cycle - NOMORE(ocr_val, 255); // limit to max the Atmel PWM will support - if (spindle_laser_power <= SPEED_POWER_MIN) - ocr_val = (SPEED_POWER_MIN - (SPEED_POWER_INTERCEPT)) * (1.0 / (SPEED_POWER_SLOPE)); // minimum setting - if (spindle_laser_power >= SPEED_POWER_MAX) - ocr_val = (SPEED_POWER_MAX - (SPEED_POWER_INTERCEPT)) * (1.0 / (SPEED_POWER_SLOPE)); // limit to max RPM - if (SPINDLE_LASER_PWM_INVERT) ocr_val = 255 - ocr_val; - WRITE(SPINDLE_LASER_ENABLE_PIN, SPINDLE_LASER_ENABLE_INVERT); // turn spindle on (active low) - analogWrite(SPINDLE_LASER_PWM_PIN, ocr_val & 0xFF); // only write low byte - delay_for_power_up(); - } - } - #else - WRITE(SPINDLE_LASER_ENABLE_PIN, SPINDLE_LASER_ENABLE_INVERT); // turn spindle on (active low) if spindle speed option not enabled - delay_for_power_up(); - #endif - } - - /** - * M5 turn off spindle - */ - inline void gcode_M5() { - stepper.synchronize(); - WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); - delay_for_power_down(); - } - -#endif // SPINDLE_LASER_ENABLE - -/** - * M17: Enable power on all stepper motors - */ -inline void gcode_M17() { - LCD_MESSAGEPGM(MSG_NO_MOVE); - enable_all_steppers(); -} - -#if IS_KINEMATIC - #define RUNPLAN(RATE_MM_S) planner.buffer_line_kinematic(destination, RATE_MM_S, active_extruder) -#else - #define RUNPLAN(RATE_MM_S) line_to_destination(RATE_MM_S) -#endif - -#if ENABLED(ADVANCED_PAUSE_FEATURE) - - static float resume_position[XYZE]; - static bool move_away_flag = false; - #if ENABLED(SDSUPPORT) - static bool sd_print_paused = false; - #endif - - static void filament_change_beep(const int8_t max_beep_count, const bool init=false) { - static millis_t next_buzz = 0; - static int8_t runout_beep = 0; - - if (init) next_buzz = runout_beep = 0; - - const millis_t ms = millis(); - if (ELAPSED(ms, next_buzz)) { - if (max_beep_count < 0 || runout_beep < max_beep_count + 5) { // Only beep as long as we're supposed to - next_buzz = ms + ((max_beep_count < 0 || runout_beep < max_beep_count) ? 2500 : 400); - BUZZ(300, 2000); - runout_beep++; - } - } - } - - static void ensure_safe_temperature() { - bool heaters_heating = true; - - wait_for_heatup = true; // M108 will clear this - while (wait_for_heatup && heaters_heating) { - idle(); - heaters_heating = false; - HOTEND_LOOP() { - if (thermalManager.degTargetHotend(e) && abs(thermalManager.degHotend(e) - thermalManager.degTargetHotend(e)) > TEMP_HYSTERESIS) { - heaters_heating = true; - #if ENABLED(ULTIPANEL) - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_WAIT_FOR_NOZZLES_TO_HEAT); - #endif - break; - } - } - } - } - - static bool pause_print(const float &retract, const float &z_lift, const float &x_pos, const float &y_pos, - const float &unload_length = 0 , const int8_t max_beep_count = 0, const bool show_lcd = false - ) { - if (move_away_flag) return false; // already paused - - if (!DEBUGGING(DRYRUN) && (unload_length != 0 || retract != 0)) { - #if ENABLED(PREVENT_COLD_EXTRUSION) - if (!thermalManager.allow_cold_extrude && - thermalManager.degTargetHotend(active_extruder) < thermalManager.extrude_min_temp) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_TOO_COLD_FOR_M600); - return false; - } - #endif - - ensure_safe_temperature(); // wait for extruder to heat up before unloading - } - - // Indicate that the printer is paused - move_away_flag = true; - - // Pause the print job and timer - #if ENABLED(SDSUPPORT) - if (card.sdprinting) { - card.pauseSDPrint(); - sd_print_paused = true; - } - #endif - print_job_timer.pause(); - - // Show initial message and wait for synchronize steppers - if (show_lcd) { - #if ENABLED(ULTIPANEL) - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INIT); - #endif - } - - // Save current position - stepper.synchronize(); - COPY(resume_position, current_position); - - if (retract) { - // Initial retract before move to filament change position - set_destination_to_current(); - destination[E_AXIS] += retract; - RUNPLAN(PAUSE_PARK_RETRACT_FEEDRATE); - stepper.synchronize(); - } - - // Lift Z axis - if (z_lift > 0) - do_blocking_move_to_z(current_position[Z_AXIS] + z_lift, PAUSE_PARK_Z_FEEDRATE); - - // Move XY axes to filament exchange position - do_blocking_move_to_xy(x_pos, y_pos, PAUSE_PARK_XY_FEEDRATE); - - if (unload_length != 0) { - if (show_lcd) { - #if ENABLED(ULTIPANEL) - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_UNLOAD); - idle(); - #endif - } - - // Unload filament - set_destination_to_current(); - destination[E_AXIS] += unload_length; - RUNPLAN(FILAMENT_CHANGE_UNLOAD_FEEDRATE); - stepper.synchronize(); - } - - if (show_lcd) { - #if ENABLED(ULTIPANEL) - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INSERT); - #endif - } - - #if HAS_BUZZER - filament_change_beep(max_beep_count, true); - #endif - - idle(); - - // Disable extruders steppers for manual filament changing (only on boards that have separate ENABLE_PINS) - #if E0_ENABLE_PIN != X_ENABLE_PIN && E1_ENABLE_PIN != Y_ENABLE_PIN - disable_e_steppers(); - safe_delay(100); - #endif - - // Start the heater idle timers - const millis_t nozzle_timeout = (millis_t)(PAUSE_PARK_NOZZLE_TIMEOUT) * 1000UL; - - HOTEND_LOOP() - thermalManager.start_heater_idle_timer(e, nozzle_timeout); - - return true; - } - - static void wait_for_filament_reload(const int8_t max_beep_count = 0) { - bool nozzle_timed_out = false; - - // Wait for filament insert by user and press button - KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = true; // LCD click or M108 will clear this - while (wait_for_user) { - #if HAS_BUZZER - filament_change_beep(max_beep_count); - #endif - - // If the nozzle has timed out, wait for the user to press the button to re-heat the nozzle, then - // re-heat the nozzle, re-show the insert screen, restart the idle timers, and start over - if (!nozzle_timed_out) - HOTEND_LOOP() - nozzle_timed_out |= thermalManager.is_heater_idle(e); - - if (nozzle_timed_out) { - #if ENABLED(ULTIPANEL) - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_CLICK_TO_HEAT_NOZZLE); - #endif - - // Wait for LCD click or M108 - while (wait_for_user) idle(true); - - // Re-enable the heaters if they timed out - HOTEND_LOOP() thermalManager.reset_heater_idle_timer(e); - - // Wait for the heaters to reach the target temperatures - ensure_safe_temperature(); - - #if ENABLED(ULTIPANEL) - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INSERT); - #endif - - // Start the heater idle timers - const millis_t nozzle_timeout = (millis_t)(PAUSE_PARK_NOZZLE_TIMEOUT) * 1000UL; - - HOTEND_LOOP() - thermalManager.start_heater_idle_timer(e, nozzle_timeout); - - wait_for_user = true; /* Wait for user to load filament */ - nozzle_timed_out = false; - - #if HAS_BUZZER - filament_change_beep(max_beep_count, true); - #endif - } - - idle(true); - } - KEEPALIVE_STATE(IN_HANDLER); - } - - static void resume_print(const float &load_length = 0, const float &initial_extrude_length = 0, const int8_t max_beep_count = 0) { - bool nozzle_timed_out = false; - - if (!move_away_flag) return; - - // Re-enable the heaters if they timed out - HOTEND_LOOP() { - nozzle_timed_out |= thermalManager.is_heater_idle(e); - thermalManager.reset_heater_idle_timer(e); - } - - if (nozzle_timed_out) ensure_safe_temperature(); - - #if HAS_BUZZER - filament_change_beep(max_beep_count, true); - #endif - - if (load_length != 0) { - #if ENABLED(ULTIPANEL) - // Show "insert filament" - if (nozzle_timed_out) - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INSERT); - #endif - - KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = true; // LCD click or M108 will clear this - while (wait_for_user && nozzle_timed_out) { - #if HAS_BUZZER - filament_change_beep(max_beep_count); - #endif - idle(true); - } - KEEPALIVE_STATE(IN_HANDLER); - - #if ENABLED(ULTIPANEL) - // Show "load" message - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_LOAD); - #endif - - // Load filament - destination[E_AXIS] += load_length; - RUNPLAN(FILAMENT_CHANGE_LOAD_FEEDRATE); - stepper.synchronize(); - } - - #if ENABLED(ULTIPANEL) && ADVANCED_PAUSE_EXTRUDE_LENGTH > 0 - - float extrude_length = initial_extrude_length; - - do { - if (extrude_length > 0) { - // "Wait for filament extrude" - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_EXTRUDE); - - // Extrude filament to get into hotend - destination[E_AXIS] += extrude_length; - RUNPLAN(ADVANCED_PAUSE_EXTRUDE_FEEDRATE); - stepper.synchronize(); - } - - // Show "Extrude More" / "Resume" menu and wait for reply - KEEPALIVE_STATE(PAUSED_FOR_USER); - wait_for_user = false; - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_OPTION); - while (advanced_pause_menu_response == ADVANCED_PAUSE_RESPONSE_WAIT_FOR) idle(true); - KEEPALIVE_STATE(IN_HANDLER); - - extrude_length = ADVANCED_PAUSE_EXTRUDE_LENGTH; - - // Keep looping if "Extrude More" was selected - } while (advanced_pause_menu_response == ADVANCED_PAUSE_RESPONSE_EXTRUDE_MORE); - - #endif - - #if ENABLED(ULTIPANEL) - // "Wait for print to resume" - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_RESUME); - #endif - - // Set extruder to saved position - destination[E_AXIS] = current_position[E_AXIS] = resume_position[E_AXIS]; - planner.set_e_position_mm(current_position[E_AXIS]); - - // Move XY to starting position, then Z - do_blocking_move_to_xy(resume_position[X_AXIS], resume_position[Y_AXIS], PAUSE_PARK_XY_FEEDRATE); - do_blocking_move_to_z(resume_position[Z_AXIS], PAUSE_PARK_Z_FEEDRATE); - - #if ENABLED(FILAMENT_RUNOUT_SENSOR) - filament_ran_out = false; - #endif - - #if ENABLED(ULTIPANEL) - // Show status screen - lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS); - #endif - - #if ENABLED(SDSUPPORT) - if (sd_print_paused) { - card.startFileprint(); - sd_print_paused = false; - } - #endif - - move_away_flag = false; - } -#endif // ADVANCED_PAUSE_FEATURE - -#if ENABLED(SDSUPPORT) - - /** - * M20: List SD card to serial output - */ - inline void gcode_M20() { - SERIAL_PROTOCOLLNPGM(MSG_BEGIN_FILE_LIST); - card.ls(); - SERIAL_PROTOCOLLNPGM(MSG_END_FILE_LIST); - } - - /** - * M21: Init SD Card - */ - inline void gcode_M21() { card.initsd(); } - - /** - * M22: Release SD Card - */ - inline void gcode_M22() { card.release(); } - - /** - * M23: Open a file - */ - inline void gcode_M23() { - // Simplify3D includes the size, so zero out all spaces (#7227) - for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0'; - card.openFile(parser.string_arg, true); - } - - /** - * M24: Start or Resume SD Print - */ - inline void gcode_M24() { - #if ENABLED(PARK_HEAD_ON_PAUSE) - resume_print(); - #endif - - card.startFileprint(); - print_job_timer.start(); - } - - /** - * M25: Pause SD Print - */ - inline void gcode_M25() { - card.pauseSDPrint(); - print_job_timer.pause(); - - #if ENABLED(PARK_HEAD_ON_PAUSE) - enqueue_and_echo_commands_P(PSTR("M125")); // Must be enqueued with pauseSDPrint set to be last in the buffer - #endif - } - - /** - * M26: Set SD Card file index - */ - inline void gcode_M26() { - if (card.cardOK && parser.seenval('S')) - card.setIndex(parser.value_long()); - } - - /** - * M27: Get SD Card status - */ - inline void gcode_M27() { card.getStatus(); } - - /** - * M28: Start SD Write - */ - inline void gcode_M28() { card.openFile(parser.string_arg, false); } - - /** - * M29: Stop SD Write - * Processed in write to file routine above - */ - inline void gcode_M29() { - // card.saving = false; - } - - /** - * M30 : Delete SD Card file - */ - inline void gcode_M30() { - if (card.cardOK) { - card.closefile(); - card.removeFile(parser.string_arg); - } - } - -#endif // SDSUPPORT - -/** - * M31: Get the time since the start of SD Print (or last M109) - */ -inline void gcode_M31() { - char buffer[21]; - duration_t elapsed = print_job_timer.duration(); - elapsed.toString(buffer); - lcd_setstatus(buffer); - - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR("Print time: ", buffer); -} - -#if ENABLED(SDSUPPORT) - - /** - * M32: Select file and start SD Print - */ - inline void gcode_M32() { - if (card.sdprinting) - stepper.synchronize(); - - char* namestartpos = parser.string_arg; - const bool call_procedure = parser.boolval('P'); - - if (card.cardOK) { - card.openFile(namestartpos, true, call_procedure); - - if (parser.seenval('S')) - card.setIndex(parser.value_long()); - - card.startFileprint(); - - // Procedure calls count as normal print time. - if (!call_procedure) print_job_timer.start(); - } - } - - #if ENABLED(LONG_FILENAME_HOST_SUPPORT) - - /** - * M33: Get the long full path of a file or folder - * - * Parameters: - * Case-insensitive DOS-style path to a file or folder - * - * Example: - * M33 miscel~1/armchair/armcha~1.gco - * - * Output: - * /Miscellaneous/Armchair/Armchair.gcode - */ - inline void gcode_M33() { - card.printLongPath(parser.string_arg); - } - - #endif - - #if ENABLED(SDCARD_SORT_ALPHA) && ENABLED(SDSORT_GCODE) - /** - * M34: Set SD Card Sorting Options - */ - inline void gcode_M34() { - if (parser.seen('S')) card.setSortOn(parser.value_bool()); - if (parser.seenval('F')) { - const int v = parser.value_long(); - card.setSortFolders(v < 0 ? -1 : v > 0 ? 1 : 0); - } - //if (parser.seen('R')) card.setSortReverse(parser.value_bool()); - } - #endif // SDCARD_SORT_ALPHA && SDSORT_GCODE - - /** - * M928: Start SD Write - */ - inline void gcode_M928() { - card.openLogFile(parser.string_arg); - } - -#endif // SDSUPPORT - -/** - * Sensitive pin test for M42, M226 - */ -static bool pin_is_protected(const int8_t pin) { - static const int8_t sensitive_pins[] PROGMEM = SENSITIVE_PINS; - for (uint8_t i = 0; i < COUNT(sensitive_pins); i++) - if (pin == (int8_t)pgm_read_byte(&sensitive_pins[i])) return true; - return false; -} - -/** - * M42: Change pin status via GCode - * - * P Pin number (LED if omitted) - * S Pin status from 0 - 255 - */ -inline void gcode_M42() { - if (!parser.seenval('S')) return; - const byte pin_status = parser.value_byte(); - - const int pin_number = parser.intval('P', LED_PIN); - if (pin_number < 0) return; - - if (pin_is_protected(pin_number)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_PROTECTED_PIN); - return; - } - - pinMode(pin_number, OUTPUT); - digitalWrite(pin_number, pin_status); - analogWrite(pin_number, pin_status); - - #if FAN_COUNT > 0 - switch (pin_number) { - #if HAS_FAN0 - case FAN_PIN: fanSpeeds[0] = pin_status; break; - #endif - #if HAS_FAN1 - case FAN1_PIN: fanSpeeds[1] = pin_status; break; - #endif - #if HAS_FAN2 - case FAN2_PIN: fanSpeeds[2] = pin_status; break; - #endif - } - #endif -} - -#if ENABLED(PINS_DEBUGGING) - - #include "pinsDebug.h" - - inline void toggle_pins() { - const bool I_flag = parser.boolval('I'); - const int repeat = parser.intval('R', 1), - start = parser.intval('S'), - end = parser.intval('E', NUM_DIGITAL_PINS - 1), - wait = parser.intval('W', 500); - - for (uint8_t pin = start; pin <= end; pin++) { - //report_pin_state_extended(pin, I_flag, false); - if (!VALID_PIN(pin)) continue; - if (!I_flag && pin_is_protected(pin)) { - report_pin_state_extended(pin, I_flag, true, "Untouched "); - SERIAL_EOL(); - } - else { - report_pin_state_extended(pin, I_flag, true, "Pulsing "); - #if AVR_AT90USB1286_FAMILY // Teensy IDEs don't know about these pins so must use FASTIO - if (pin == TEENSY_E2) { - SET_OUTPUT(TEENSY_E2); - for (int16_t j = 0; j < repeat; j++) { - WRITE(TEENSY_E2, LOW); safe_delay(wait); - WRITE(TEENSY_E2, HIGH); safe_delay(wait); - WRITE(TEENSY_E2, LOW); safe_delay(wait); - } - } - else if (pin == TEENSY_E3) { - SET_OUTPUT(TEENSY_E3); - for (int16_t j = 0; j < repeat; j++) { - WRITE(TEENSY_E3, LOW); safe_delay(wait); - WRITE(TEENSY_E3, HIGH); safe_delay(wait); - WRITE(TEENSY_E3, LOW); safe_delay(wait); - } - } - else - #endif - { - pinMode(pin, OUTPUT); - for (int16_t j = 0; j < repeat; j++) { - digitalWrite(pin, 0); safe_delay(wait); - digitalWrite(pin, 1); safe_delay(wait); - digitalWrite(pin, 0); safe_delay(wait); - } - } - - } - SERIAL_EOL(); - } - SERIAL_ECHOLNPGM("Done."); - - } // toggle_pins - - inline void servo_probe_test() { - #if !(NUM_SERVOS > 0 && HAS_SERVO_0) - - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM("SERVO not setup"); - - #elif !HAS_Z_SERVO_ENDSTOP - - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM("Z_ENDSTOP_SERVO_NR not setup"); - - #else // HAS_Z_SERVO_ENDSTOP - - const uint8_t probe_index = parser.byteval('P', Z_ENDSTOP_SERVO_NR); - - SERIAL_PROTOCOLLNPGM("Servo probe test"); - SERIAL_PROTOCOLLNPAIR(". using index: ", probe_index); - SERIAL_PROTOCOLLNPAIR(". deploy angle: ", z_servo_angle[0]); - SERIAL_PROTOCOLLNPAIR(". stow angle: ", z_servo_angle[1]); - - bool probe_inverting; - - #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) - - #define PROBE_TEST_PIN Z_MIN_PIN - - SERIAL_PROTOCOLLNPAIR(". probe uses Z_MIN pin: ", PROBE_TEST_PIN); - SERIAL_PROTOCOLLNPGM(". uses Z_MIN_ENDSTOP_INVERTING (ignores Z_MIN_PROBE_ENDSTOP_INVERTING)"); - SERIAL_PROTOCOLPGM(". Z_MIN_ENDSTOP_INVERTING: "); - - #if Z_MIN_ENDSTOP_INVERTING - SERIAL_PROTOCOLLNPGM("true"); - #else - SERIAL_PROTOCOLLNPGM("false"); - #endif - - probe_inverting = Z_MIN_ENDSTOP_INVERTING; - - #elif ENABLED(Z_MIN_PROBE_ENDSTOP) - - #define PROBE_TEST_PIN Z_MIN_PROBE_PIN - SERIAL_PROTOCOLLNPAIR(". probe uses Z_MIN_PROBE_PIN: ", PROBE_TEST_PIN); - SERIAL_PROTOCOLLNPGM(". uses Z_MIN_PROBE_ENDSTOP_INVERTING (ignores Z_MIN_ENDSTOP_INVERTING)"); - SERIAL_PROTOCOLPGM(". Z_MIN_PROBE_ENDSTOP_INVERTING: "); - - #if Z_MIN_PROBE_ENDSTOP_INVERTING - SERIAL_PROTOCOLLNPGM("true"); - #else - SERIAL_PROTOCOLLNPGM("false"); - #endif - - probe_inverting = Z_MIN_PROBE_ENDSTOP_INVERTING; - - #endif - - SERIAL_PROTOCOLLNPGM(". deploy & stow 4 times"); - SET_INPUT_PULLUP(PROBE_TEST_PIN); - bool deploy_state, stow_state; - for (uint8_t i = 0; i < 4; i++) { - MOVE_SERVO(probe_index, z_servo_angle[0]); //deploy - safe_delay(500); - deploy_state = READ(PROBE_TEST_PIN); - MOVE_SERVO(probe_index, z_servo_angle[1]); //stow - safe_delay(500); - stow_state = READ(PROBE_TEST_PIN); - } - if (probe_inverting != deploy_state) SERIAL_PROTOCOLLNPGM("WARNING - INVERTING setting probably backwards"); - - refresh_cmd_timeout(); - - if (deploy_state != stow_state) { - SERIAL_PROTOCOLLNPGM("BLTouch clone detected"); - if (deploy_state) { - SERIAL_PROTOCOLLNPGM(". DEPLOYED state: HIGH (logic 1)"); - SERIAL_PROTOCOLLNPGM(". STOWED (triggered) state: LOW (logic 0)"); - } - else { - SERIAL_PROTOCOLLNPGM(". DEPLOYED state: LOW (logic 0)"); - SERIAL_PROTOCOLLNPGM(". STOWED (triggered) state: HIGH (logic 1)"); - } - #if ENABLED(BLTOUCH) - SERIAL_PROTOCOLLNPGM("ERROR: BLTOUCH enabled - set this device up as a Z Servo Probe with inverting as true."); - #endif - - } - else { // measure active signal length - MOVE_SERVO(probe_index, z_servo_angle[0]); // deploy - safe_delay(500); - SERIAL_PROTOCOLLNPGM("please trigger probe"); - uint16_t probe_counter = 0; - - // Allow 30 seconds max for operator to trigger probe - for (uint16_t j = 0; j < 500 * 30 && probe_counter == 0 ; j++) { - - safe_delay(2); - - if (0 == j % (500 * 1)) // keep cmd_timeout happy - refresh_cmd_timeout(); - - if (deploy_state != READ(PROBE_TEST_PIN)) { // probe triggered - - for (probe_counter = 1; probe_counter < 50 && deploy_state != READ(PROBE_TEST_PIN); ++probe_counter) - safe_delay(2); - - if (probe_counter == 50) - SERIAL_PROTOCOLLNPGM("Z Servo Probe detected"); // >= 100mS active time - else if (probe_counter >= 2) - SERIAL_PROTOCOLLNPAIR("BLTouch compatible probe detected - pulse width (+/- 4mS): ", probe_counter * 2); // allow 4 - 100mS pulse - else - SERIAL_PROTOCOLLNPGM("noise detected - please re-run test"); // less than 2mS pulse - - MOVE_SERVO(probe_index, z_servo_angle[1]); //stow - - } // pulse detected - - } // for loop waiting for trigger - - if (probe_counter == 0) SERIAL_PROTOCOLLNPGM("trigger not detected"); - - } // measure active signal length - - #endif - - } // servo_probe_test - - /** - * M43: Pin debug - report pin state, watch pins, toggle pins and servo probe test/report - * - * M43 - report name and state of pin(s) - * P Pin to read or watch. If omitted, reads all pins. - * I Flag to ignore Marlin's pin protection. - * - * M43 W - Watch pins -reporting changes- until reset, click, or M108. - * P Pin to read or watch. If omitted, read/watch all pins. - * I Flag to ignore Marlin's pin protection. - * - * M43 E - Enable / disable background endstop monitoring - * - Machine continues to operate - * - Reports changes to endstops - * - Toggles LED_PIN when an endstop changes - * - Can not reliably catch the 5mS pulse from BLTouch type probes - * - * M43 T - Toggle pin(s) and report which pin is being toggled - * S - Start Pin number. If not given, will default to 0 - * L - End Pin number. If not given, will default to last pin defined for this board - * I - Flag to ignore Marlin's pin protection. Use with caution!!!! - * R - Repeat pulses on each pin this number of times before continueing to next pin - * W - Wait time (in miliseconds) between pulses. If not given will default to 500 - * - * M43 S - Servo probe test - * P - Probe index (optional - defaults to 0 - */ - inline void gcode_M43() { - - if (parser.seen('T')) { // must be first or else its "S" and "E" parameters will execute endstop or servo test - toggle_pins(); - return; - } - - // Enable or disable endstop monitoring - if (parser.seen('E')) { - endstop_monitor_flag = parser.value_bool(); - SERIAL_PROTOCOLPGM("endstop monitor "); - serialprintPGM(endstop_monitor_flag ? PSTR("en") : PSTR("dis")); - SERIAL_PROTOCOLLNPGM("abled"); - return; - } - - if (parser.seen('S')) { - servo_probe_test(); - return; - } - - // Get the range of pins to test or watch - const uint8_t first_pin = parser.byteval('P'), - last_pin = parser.seenval('P') ? first_pin : NUM_DIGITAL_PINS - 1; - - if (first_pin > last_pin) return; - - const bool ignore_protection = parser.boolval('I'); - - // Watch until click, M108, or reset - if (parser.boolval('W')) { - SERIAL_PROTOCOLLNPGM("Watching pins"); - uint8_t pin_state[last_pin - first_pin + 1]; - for (int8_t pin = first_pin; pin <= last_pin; pin++) { - if (!VALID_PIN(pin)) continue; - if (pin_is_protected(pin) && !ignore_protection) 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[pin - first_pin] = digitalRead(pin); - } - - #if HAS_RESUME_CONTINUE - wait_for_user = true; - KEEPALIVE_STATE(PAUSED_FOR_USER); - #endif - - for (;;) { - for (int8_t pin = first_pin; pin <= last_pin; pin++) { - if (!VALID_PIN(pin)) continue; - if (pin_is_protected(pin) && !ignore_protection) continue; - const byte val = - /* - IS_ANALOG(pin) - ? analogRead(DIGITAL_PIN_TO_ANALOG_PIN(pin)) : // int16_t val - : - //*/ - digitalRead(pin); - if (val != pin_state[pin - first_pin]) { - report_pin_state_extended(pin, ignore_protection, false); - pin_state[pin - first_pin] = val; - } - } - - #if HAS_RESUME_CONTINUE - if (!wait_for_user) { - KEEPALIVE_STATE(IN_HANDLER); - break; - } - #endif - - safe_delay(200); - } - return; - } - - // Report current state of selected pin(s) - for (uint8_t pin = first_pin; pin <= last_pin; pin++) - if (VALID_PIN(pin)) report_pin_state_extended(pin, ignore_protection, true); - } - -#endif // PINS_DEBUGGING - -#if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) - - /** - * M48: Z probe repeatability measurement function. - * - * Usage: - * M48 - * P = Number of sampled points (4-50, default 10) - * X = Sample X position - * Y = Sample Y position - * V = Verbose level (0-4, default=1) - * E = Engage Z probe for each reading - * L = Number of legs of movement before probe - * S = Schizoid (Or Star if you prefer) - * - * This function assumes the bed has been homed. Specifically, that a G28 command - * as been issued prior to invoking the M48 Z probe repeatability measurement function. - * Any information generated by a prior G29 Bed leveling command will be lost and need to be - * regenerated. - */ - inline void gcode_M48() { - - if (axis_unhomed_error()) return; - - const int8_t verbose_level = parser.byteval('V', 1); - if (!WITHIN(verbose_level, 0, 4)) { - SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-4)."); - return; - } - - if (verbose_level > 0) - SERIAL_PROTOCOLLNPGM("M48 Z-Probe Repeatability Test"); - - const int8_t n_samples = parser.byteval('P', 10); - if (!WITHIN(n_samples, 4, 50)) { - SERIAL_PROTOCOLLNPGM("?Sample size not plausible (4-50)."); - return; - } - - const bool stow_probe_after_each = parser.boolval('E'); - - float X_current = current_position[X_AXIS], - Y_current = current_position[Y_AXIS]; - - const float X_probe_location = parser.linearval('X', X_current + X_PROBE_OFFSET_FROM_EXTRUDER), - Y_probe_location = parser.linearval('Y', Y_current + Y_PROBE_OFFSET_FROM_EXTRUDER); - - #if DISABLED(DELTA) - if (!WITHIN(X_probe_location, LOGICAL_X_POSITION(MIN_PROBE_X), LOGICAL_X_POSITION(MAX_PROBE_X))) { - out_of_range_error(PSTR("X")); - return; - } - if (!WITHIN(Y_probe_location, LOGICAL_Y_POSITION(MIN_PROBE_Y), LOGICAL_Y_POSITION(MAX_PROBE_Y))) { - out_of_range_error(PSTR("Y")); - return; - } - #else - if (!position_is_reachable_by_probe_xy(X_probe_location, Y_probe_location)) { - SERIAL_PROTOCOLLNPGM("? (X,Y) location outside of probeable radius."); - return; - } - #endif - - bool seen_L = parser.seen('L'); - uint8_t n_legs = seen_L ? parser.value_byte() : 0; - if (n_legs > 15) { - SERIAL_PROTOCOLLNPGM("?Number of legs in movement not plausible (0-15)."); - return; - } - if (n_legs == 1) n_legs = 2; - - const bool schizoid_flag = parser.boolval('S'); - if (schizoid_flag && !seen_L) n_legs = 7; - - /** - * Now get everything to the specified probe point So we can safely do a - * probe to get us close to the bed. If the Z-Axis is far from the bed, - * we don't want to use that as a starting point for each probe. - */ - if (verbose_level > 2) - SERIAL_PROTOCOLLNPGM("Positioning the probe..."); - - // Disable bed level correction in M48 because we want the raw data when we probe - - #if HAS_LEVELING - const bool was_enabled = leveling_is_active(); - set_bed_leveling_enabled(false); - #endif - - setup_for_endstop_or_probe_move(); - - double mean = 0.0, sigma = 0.0, min = 99999.9, max = -99999.9, sample_set[n_samples]; - - // Move to the first point, deploy, and probe - const float t = probe_pt(X_probe_location, Y_probe_location, stow_probe_after_each, verbose_level); - bool probing_good = !isnan(t); - - if (probing_good) { - randomSeed(millis()); - - for (uint8_t n = 0; n < n_samples; n++) { - if (n_legs) { - const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise - float angle = random(0.0, 360.0); - const float radius = random( - #if ENABLED(DELTA) - 0.1250000000 * (DELTA_PROBEABLE_RADIUS), - 0.3333333333 * (DELTA_PROBEABLE_RADIUS) - #else - 5.0, 0.125 * min(X_BED_SIZE, Y_BED_SIZE) - #endif - ); - - if (verbose_level > 3) { - SERIAL_ECHOPAIR("Starting radius: ", radius); - SERIAL_ECHOPAIR(" angle: ", angle); - SERIAL_ECHOPGM(" Direction: "); - if (dir > 0) SERIAL_ECHOPGM("Counter-"); - SERIAL_ECHOLNPGM("Clockwise"); - } - - for (uint8_t l = 0; l < n_legs - 1; l++) { - double delta_angle; - - if (schizoid_flag) - // The points of a 5 point star are 72 degrees apart. We need to - // skip a point and go to the next one on the star. - delta_angle = dir * 2.0 * 72.0; - - else - // If we do this line, we are just trying to move further - // around the circle. - delta_angle = dir * (float) random(25, 45); - - angle += delta_angle; - - while (angle > 360.0) // We probably do not need to keep the angle between 0 and 2*PI, but the - angle -= 360.0; // Arduino documentation says the trig functions should not be given values - while (angle < 0.0) // outside of this range. It looks like they behave correctly with - angle += 360.0; // numbers outside of the range, but just to be safe we clamp them. - - X_current = X_probe_location - (X_PROBE_OFFSET_FROM_EXTRUDER) + cos(RADIANS(angle)) * radius; - Y_current = Y_probe_location - (Y_PROBE_OFFSET_FROM_EXTRUDER) + sin(RADIANS(angle)) * radius; - - #if DISABLED(DELTA) - X_current = constrain(X_current, X_MIN_POS, X_MAX_POS); - Y_current = constrain(Y_current, Y_MIN_POS, Y_MAX_POS); - #else - // If we have gone out too far, we can do a simple fix and scale the numbers - // back in closer to the origin. - while (!position_is_reachable_by_probe_xy(X_current, Y_current)) { - X_current *= 0.8; - Y_current *= 0.8; - if (verbose_level > 3) { - SERIAL_ECHOPAIR("Pulling point towards center:", X_current); - SERIAL_ECHOLNPAIR(", ", Y_current); - } - } - #endif - if (verbose_level > 3) { - SERIAL_PROTOCOLPGM("Going to:"); - SERIAL_ECHOPAIR(" X", X_current); - SERIAL_ECHOPAIR(" Y", Y_current); - SERIAL_ECHOLNPAIR(" Z", current_position[Z_AXIS]); - } - do_blocking_move_to_xy(X_current, Y_current); - } // n_legs loop - } // n_legs - - // Probe a single point - sample_set[n] = probe_pt(X_probe_location, Y_probe_location, stow_probe_after_each, 0); - - // Break the loop if the probe fails - probing_good = !isnan(sample_set[n]); - if (!probing_good) break; - - /** - * Get the current mean for the data points we have so far - */ - double sum = 0.0; - for (uint8_t j = 0; j <= n; j++) sum += sample_set[j]; - mean = sum / (n + 1); - - NOMORE(min, sample_set[n]); - NOLESS(max, sample_set[n]); - - /** - * Now, use that mean to calculate the standard deviation for the - * data points we have so far - */ - sum = 0.0; - for (uint8_t j = 0; j <= n; j++) - sum += sq(sample_set[j] - mean); - - sigma = SQRT(sum / (n + 1)); - if (verbose_level > 0) { - if (verbose_level > 1) { - SERIAL_PROTOCOL(n + 1); - SERIAL_PROTOCOLPGM(" of "); - SERIAL_PROTOCOL((int)n_samples); - SERIAL_PROTOCOLPGM(": z: "); - SERIAL_PROTOCOL_F(sample_set[n], 3); - if (verbose_level > 2) { - SERIAL_PROTOCOLPGM(" mean: "); - SERIAL_PROTOCOL_F(mean, 4); - SERIAL_PROTOCOLPGM(" sigma: "); - SERIAL_PROTOCOL_F(sigma, 6); - SERIAL_PROTOCOLPGM(" min: "); - SERIAL_PROTOCOL_F(min, 3); - SERIAL_PROTOCOLPGM(" max: "); - SERIAL_PROTOCOL_F(max, 3); - SERIAL_PROTOCOLPGM(" range: "); - SERIAL_PROTOCOL_F(max-min, 3); - } - SERIAL_EOL(); - } - } - - } // n_samples loop - } - - STOW_PROBE(); - - if (probing_good) { - SERIAL_PROTOCOLLNPGM("Finished!"); - - if (verbose_level > 0) { - SERIAL_PROTOCOLPGM("Mean: "); - SERIAL_PROTOCOL_F(mean, 6); - SERIAL_PROTOCOLPGM(" Min: "); - SERIAL_PROTOCOL_F(min, 3); - SERIAL_PROTOCOLPGM(" Max: "); - SERIAL_PROTOCOL_F(max, 3); - SERIAL_PROTOCOLPGM(" Range: "); - SERIAL_PROTOCOL_F(max-min, 3); - SERIAL_EOL(); - } - - SERIAL_PROTOCOLPGM("Standard Deviation: "); - SERIAL_PROTOCOL_F(sigma, 6); - SERIAL_EOL(); - SERIAL_EOL(); - } - - clean_up_after_endstop_or_probe_move(); - - // Re-enable bed level correction if it had been on - #if HAS_LEVELING - set_bed_leveling_enabled(was_enabled); - #endif - - report_current_position(); - } - -#endif // Z_MIN_PROBE_REPEATABILITY_TEST - -#if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(UBL_G26_MESH_VALIDATION) - - inline void gcode_M49() { - ubl.g26_debug_flag ^= true; - SERIAL_PROTOCOLPGM("UBL Debug Flag turned "); - serialprintPGM(ubl.g26_debug_flag ? PSTR("on.") : PSTR("off.")); - } - -#endif // AUTO_BED_LEVELING_UBL && UBL_G26_MESH_VALIDATION - -/** - * M75: Start print timer - */ -inline void gcode_M75() { print_job_timer.start(); } - -/** - * M76: Pause print timer - */ -inline void gcode_M76() { print_job_timer.pause(); } - -/** - * M77: Stop print timer - */ -inline void gcode_M77() { print_job_timer.stop(); } - -#if ENABLED(PRINTCOUNTER) - /** - * M78: Show print statistics - */ - inline void gcode_M78() { - // "M78 S78" will reset the statistics - if (parser.intval('S') == 78) - print_job_timer.initStats(); - else - print_job_timer.showStats(); - } -#endif - -/** - * M104: Set hot end temperature - */ -inline void gcode_M104() { - if (get_target_extruder_from_command(104)) return; - if (DEBUGGING(DRYRUN)) return; - - #if ENABLED(SINGLENOZZLE) - if (target_extruder != active_extruder) return; - #endif - - if (parser.seenval('S')) { - const int16_t temp = parser.value_celsius(); - thermalManager.setTargetHotend(temp, target_extruder); - - #if ENABLED(DUAL_X_CARRIAGE) - if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0) - thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1); - #endif - - #if ENABLED(PRINTJOB_TIMER_AUTOSTART) - /** - * Stop the timer at the end of print. Start is managed by 'heat and wait' M109. - * We use half EXTRUDE_MINTEMP here to allow nozzles to be put into hot - * standby mode, for instance in a dual extruder setup, without affecting - * the running print timer. - */ - if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) { - print_job_timer.stop(); - LCD_MESSAGEPGM(WELCOME_MSG); - } - #endif - - if (parser.value_celsius() > thermalManager.degHotend(target_extruder)) - lcd_status_printf_P(0, PSTR("E%i %s"), target_extruder + 1, MSG_HEATING); - } - - #if ENABLED(AUTOTEMP) - planner.autotemp_M104_M109(); - #endif -} - -#if HAS_TEMP_HOTEND || HAS_TEMP_BED - - void print_heater_state(const float &c, const float &t, - #if ENABLED(SHOW_TEMP_ADC_VALUES) - const float r, - #endif - const int8_t e=-2 - ) { - #if !(HAS_TEMP_BED && HAS_TEMP_HOTEND) && HOTENDS <= 1 - UNUSED(e); - #endif - - SERIAL_PROTOCOLCHAR(' '); - SERIAL_PROTOCOLCHAR( - #if HAS_TEMP_BED && HAS_TEMP_HOTEND - e == -1 ? 'B' : 'T' - #elif HAS_TEMP_HOTEND - 'T' - #else - 'B' - #endif - ); - #if HOTENDS > 1 - if (e >= 0) SERIAL_PROTOCOLCHAR('0' + e); - #endif - SERIAL_PROTOCOLCHAR(':'); - SERIAL_PROTOCOL(c); - SERIAL_PROTOCOLPAIR(" /" , t); - #if ENABLED(SHOW_TEMP_ADC_VALUES) - SERIAL_PROTOCOLPAIR(" (", r / OVERSAMPLENR); - SERIAL_PROTOCOLCHAR(')'); - #endif - } - - void print_heaterstates() { - #if HAS_TEMP_HOTEND - print_heater_state(thermalManager.degHotend(target_extruder), thermalManager.degTargetHotend(target_extruder) - #if ENABLED(SHOW_TEMP_ADC_VALUES) - , thermalManager.rawHotendTemp(target_extruder) - #endif - ); - #endif - #if HAS_TEMP_BED - print_heater_state(thermalManager.degBed(), thermalManager.degTargetBed(), - #if ENABLED(SHOW_TEMP_ADC_VALUES) - thermalManager.rawBedTemp(), - #endif - -1 // BED - ); - #endif - #if HOTENDS > 1 - HOTEND_LOOP() print_heater_state(thermalManager.degHotend(e), thermalManager.degTargetHotend(e), - #if ENABLED(SHOW_TEMP_ADC_VALUES) - thermalManager.rawHotendTemp(e), - #endif - e - ); - #endif - SERIAL_PROTOCOLPGM(" @:"); - SERIAL_PROTOCOL(thermalManager.getHeaterPower(target_extruder)); - #if HAS_TEMP_BED - SERIAL_PROTOCOLPGM(" B@:"); - SERIAL_PROTOCOL(thermalManager.getHeaterPower(-1)); - #endif - #if HOTENDS > 1 - HOTEND_LOOP() { - SERIAL_PROTOCOLPAIR(" @", e); - SERIAL_PROTOCOLCHAR(':'); - SERIAL_PROTOCOL(thermalManager.getHeaterPower(e)); - } - #endif - } -#endif - -/** - * M105: Read hot end and bed temperature - */ -inline void gcode_M105() { - if (get_target_extruder_from_command(105)) return; - - #if HAS_TEMP_HOTEND || HAS_TEMP_BED - SERIAL_PROTOCOLPGM(MSG_OK); - print_heaterstates(); - #else // !HAS_TEMP_HOTEND && !HAS_TEMP_BED - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_NO_THERMISTORS); - #endif - - SERIAL_EOL(); -} - -#if ENABLED(AUTO_REPORT_TEMPERATURES) && (HAS_TEMP_HOTEND || HAS_TEMP_BED) - - static uint8_t auto_report_temp_interval; - static millis_t next_temp_report_ms; - - /** - * M155: Set temperature auto-report interval. M155 S - */ - inline void gcode_M155() { - if (parser.seenval('S')) { - auto_report_temp_interval = parser.value_byte(); - NOMORE(auto_report_temp_interval, 60); - next_temp_report_ms = millis() + 1000UL * auto_report_temp_interval; - } - } - - inline void auto_report_temperatures() { - if (auto_report_temp_interval && ELAPSED(millis(), next_temp_report_ms)) { - next_temp_report_ms = millis() + 1000UL * auto_report_temp_interval; - print_heaterstates(); - SERIAL_EOL(); - } - } - -#endif // AUTO_REPORT_TEMPERATURES - -#if FAN_COUNT > 0 - - /** - * M106: Set Fan Speed - * - * S Speed between 0-255 - * P Fan index, if more than one fan - */ - inline void gcode_M106() { - uint16_t s = parser.ushortval('S', 255); - NOMORE(s, 255); - const uint8_t p = parser.byteval('P', 0); - if (p < FAN_COUNT) fanSpeeds[p] = s; - } - - /** - * M107: Fan Off - */ - inline void gcode_M107() { - const uint16_t p = parser.ushortval('P'); - if (p < FAN_COUNT) fanSpeeds[p] = 0; - } - -#endif // FAN_COUNT > 0 - -#if DISABLED(EMERGENCY_PARSER) - - /** - * M108: Stop the waiting for heaters in M109, M190, M303. Does not affect the target temperature. - */ - inline void gcode_M108() { wait_for_heatup = false; } - - - /** - * M112: Emergency Stop - */ - inline void gcode_M112() { kill(PSTR(MSG_KILLED)); } - - - /** - * M410: Quickstop - Abort all planned moves - * - * This will stop the carriages mid-move, so most likely they - * will be out of sync with the stepper position after this. - */ - inline void gcode_M410() { quickstop_stepper(); } - -#endif - -/** - * M109: Sxxx Wait for extruder(s) to reach temperature. Waits only when heating. - * Rxxx Wait for extruder(s) to reach temperature. Waits when heating and cooling. - */ - -#ifndef MIN_COOLING_SLOPE_DEG - #define MIN_COOLING_SLOPE_DEG 1.50 -#endif -#ifndef MIN_COOLING_SLOPE_TIME - #define MIN_COOLING_SLOPE_TIME 60 -#endif - -inline void gcode_M109() { - - if (get_target_extruder_from_command(109)) return; - if (DEBUGGING(DRYRUN)) return; - - #if ENABLED(SINGLENOZZLE) - if (target_extruder != active_extruder) return; - #endif - - const bool no_wait_for_cooling = parser.seenval('S'); - if (no_wait_for_cooling || parser.seenval('R')) { - const int16_t temp = parser.value_celsius(); - thermalManager.setTargetHotend(temp, target_extruder); - - #if ENABLED(DUAL_X_CARRIAGE) - if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0) - thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1); - #endif - - #if ENABLED(PRINTJOB_TIMER_AUTOSTART) - /** - * Use half EXTRUDE_MINTEMP to allow nozzles to be put into hot - * standby mode, (e.g., in a dual extruder setup) without affecting - * the running print timer. - */ - if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) { - print_job_timer.stop(); - LCD_MESSAGEPGM(WELCOME_MSG); - } - else - print_job_timer.start(); - #endif - - if (thermalManager.isHeatingHotend(target_extruder)) lcd_status_printf_P(0, PSTR("E%i %s"), target_extruder + 1, MSG_HEATING); - } - else return; - - #if ENABLED(AUTOTEMP) - planner.autotemp_M104_M109(); - #endif - - #if TEMP_RESIDENCY_TIME > 0 - millis_t residency_start_ms = 0; - // Loop until the temperature has stabilized - #define TEMP_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_RESIDENCY_TIME) * 1000UL)) - #else - // Loop until the temperature is very close target - #define TEMP_CONDITIONS (wants_to_cool ? thermalManager.isCoolingHotend(target_extruder) : thermalManager.isHeatingHotend(target_extruder)) - #endif - - float target_temp = -1.0, old_temp = 9999.0; - bool wants_to_cool = false; - wait_for_heatup = true; - millis_t now, next_temp_ms = 0, next_cool_check_ms = 0; - - #if DISABLED(BUSY_WHILE_HEATING) - KEEPALIVE_STATE(NOT_BUSY); - #endif - - #if ENABLED(PRINTER_EVENT_LEDS) - const float start_temp = thermalManager.degHotend(target_extruder); - uint8_t old_blue = 0; - #endif - - do { - // Target temperature might be changed during the loop - if (target_temp != thermalManager.degTargetHotend(target_extruder)) { - wants_to_cool = thermalManager.isCoolingHotend(target_extruder); - target_temp = thermalManager.degTargetHotend(target_extruder); - - // Exit if S, continue if S, R, or R - if (no_wait_for_cooling && wants_to_cool) break; - } - - now = millis(); - if (ELAPSED(now, next_temp_ms)) { //Print temp & remaining time every 1s while waiting - next_temp_ms = now + 1000UL; - print_heaterstates(); - #if TEMP_RESIDENCY_TIME > 0 - SERIAL_PROTOCOLPGM(" W:"); - if (residency_start_ms) - SERIAL_PROTOCOL(long((((TEMP_RESIDENCY_TIME) * 1000UL) - (now - residency_start_ms)) / 1000UL)); - else - SERIAL_PROTOCOLCHAR('?'); - #endif - SERIAL_EOL(); - } - - idle(); - refresh_cmd_timeout(); // to prevent stepper_inactive_time from running out - - const float temp = thermalManager.degHotend(target_extruder); - - #if ENABLED(PRINTER_EVENT_LEDS) - // Gradually change LED strip from violet to red as nozzle heats up - if (!wants_to_cool) { - const uint8_t blue = map(constrain(temp, start_temp, target_temp), start_temp, target_temp, 255, 0); - if (blue != old_blue) { - old_blue = blue; - set_led_color(255, 0, blue - #if ENABLED(NEOPIXEL_RGBW_LED) - , 0, true - #endif - ); - } - } - #endif - - #if TEMP_RESIDENCY_TIME > 0 - - const float temp_diff = FABS(target_temp - temp); - - if (!residency_start_ms) { - // Start the TEMP_RESIDENCY_TIME timer when we reach target temp for the first time. - if (temp_diff < TEMP_WINDOW) residency_start_ms = now; - } - else if (temp_diff > TEMP_HYSTERESIS) { - // Restart the timer whenever the temperature falls outside the hysteresis. - residency_start_ms = now; - } - - #endif - - // Prevent a wait-forever situation if R is misused i.e. M109 R0 - if (wants_to_cool) { - // break after MIN_COOLING_SLOPE_TIME seconds - // if the temperature did not drop at least MIN_COOLING_SLOPE_DEG - if (!next_cool_check_ms || ELAPSED(now, next_cool_check_ms)) { - if (old_temp - temp < MIN_COOLING_SLOPE_DEG) break; - next_cool_check_ms = now + 1000UL * MIN_COOLING_SLOPE_TIME; - old_temp = temp; - } - } - - } while (wait_for_heatup && TEMP_CONDITIONS); - - if (wait_for_heatup) { - LCD_MESSAGEPGM(MSG_HEATING_COMPLETE); - #if ENABLED(PRINTER_EVENT_LEDS) - #if ENABLED(RGBW_LED) || ENABLED(NEOPIXEL_RGBW_LED) - set_led_color(0, 0, 0, 255); // Turn on the WHITE LED - #else - set_led_color(255, 255, 255); // Set LEDs All On - #endif - #endif - } - - #if DISABLED(BUSY_WHILE_HEATING) - KEEPALIVE_STATE(IN_HANDLER); - #endif -} - -#if HAS_TEMP_BED - - #ifndef MIN_COOLING_SLOPE_DEG_BED - #define MIN_COOLING_SLOPE_DEG_BED 1.50 - #endif - #ifndef MIN_COOLING_SLOPE_TIME_BED - #define MIN_COOLING_SLOPE_TIME_BED 60 - #endif - - /** - * M190: Sxxx Wait for bed current temp to reach target temp. Waits only when heating - * Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling - */ - inline void gcode_M190() { - if (DEBUGGING(DRYRUN)) return; - - LCD_MESSAGEPGM(MSG_BED_HEATING); - const bool no_wait_for_cooling = parser.seenval('S'); - if (no_wait_for_cooling || parser.seenval('R')) { - thermalManager.setTargetBed(parser.value_celsius()); - #if ENABLED(PRINTJOB_TIMER_AUTOSTART) - if (parser.value_celsius() > BED_MINTEMP) - print_job_timer.start(); - #endif - } - else return; - - #if TEMP_BED_RESIDENCY_TIME > 0 - millis_t residency_start_ms = 0; - // Loop until the temperature has stabilized - #define TEMP_BED_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_BED_RESIDENCY_TIME) * 1000UL)) - #else - // Loop until the temperature is very close target - #define TEMP_BED_CONDITIONS (wants_to_cool ? thermalManager.isCoolingBed() : thermalManager.isHeatingBed()) - #endif - - float target_temp = -1.0, old_temp = 9999.0; - bool wants_to_cool = false; - wait_for_heatup = true; - millis_t now, next_temp_ms = 0, next_cool_check_ms = 0; - - #if DISABLED(BUSY_WHILE_HEATING) - KEEPALIVE_STATE(NOT_BUSY); - #endif - - target_extruder = active_extruder; // for print_heaterstates - - #if ENABLED(PRINTER_EVENT_LEDS) - const float start_temp = thermalManager.degBed(); - uint8_t old_red = 255; - #endif - - do { - // Target temperature might be changed during the loop - if (target_temp != thermalManager.degTargetBed()) { - wants_to_cool = thermalManager.isCoolingBed(); - target_temp = thermalManager.degTargetBed(); - - // Exit if S, continue if S, R, or R - if (no_wait_for_cooling && wants_to_cool) break; - } - - now = millis(); - if (ELAPSED(now, next_temp_ms)) { //Print Temp Reading every 1 second while heating up. - next_temp_ms = now + 1000UL; - print_heaterstates(); - #if TEMP_BED_RESIDENCY_TIME > 0 - SERIAL_PROTOCOLPGM(" W:"); - if (residency_start_ms) - SERIAL_PROTOCOL(long((((TEMP_BED_RESIDENCY_TIME) * 1000UL) - (now - residency_start_ms)) / 1000UL)); - else - SERIAL_PROTOCOLCHAR('?'); - #endif - SERIAL_EOL(); - } - - idle(); - refresh_cmd_timeout(); // to prevent stepper_inactive_time from running out - - const float temp = thermalManager.degBed(); - - #if ENABLED(PRINTER_EVENT_LEDS) - // Gradually change LED strip from blue to violet as bed heats up - if (!wants_to_cool) { - const uint8_t red = map(constrain(temp, start_temp, target_temp), start_temp, target_temp, 0, 255); - if (red != old_red) { - old_red = red; - set_led_color(red, 0, 255 - #if ENABLED(NEOPIXEL_RGBW_LED) - , 0, true - #endif - ); - } - } - #endif - - #if TEMP_BED_RESIDENCY_TIME > 0 - - const float temp_diff = FABS(target_temp - temp); - - if (!residency_start_ms) { - // Start the TEMP_BED_RESIDENCY_TIME timer when we reach target temp for the first time. - if (temp_diff < TEMP_BED_WINDOW) residency_start_ms = now; - } - else if (temp_diff > TEMP_BED_HYSTERESIS) { - // Restart the timer whenever the temperature falls outside the hysteresis. - residency_start_ms = now; - } - - #endif // TEMP_BED_RESIDENCY_TIME > 0 - - // Prevent a wait-forever situation if R is misused i.e. M190 R0 - if (wants_to_cool) { - // Break after MIN_COOLING_SLOPE_TIME_BED seconds - // if the temperature did not drop at least MIN_COOLING_SLOPE_DEG_BED - if (!next_cool_check_ms || ELAPSED(now, next_cool_check_ms)) { - if (old_temp - temp < MIN_COOLING_SLOPE_DEG_BED) break; - next_cool_check_ms = now + 1000UL * MIN_COOLING_SLOPE_TIME_BED; - old_temp = temp; - } - } - - } while (wait_for_heatup && TEMP_BED_CONDITIONS); - - if (wait_for_heatup) LCD_MESSAGEPGM(MSG_BED_DONE); - #if DISABLED(BUSY_WHILE_HEATING) - KEEPALIVE_STATE(IN_HANDLER); - #endif - } - -#endif // HAS_TEMP_BED - -/** - * M110: Set Current Line Number - */ -inline void gcode_M110() { - if (parser.seenval('N')) gcode_LastN = parser.value_long(); -} - -/** - * M111: Set the debug level - */ -inline void gcode_M111() { - if (parser.seen('S')) marlin_debug_flags = parser.byteval('S'); - - const static char str_debug_1[] PROGMEM = MSG_DEBUG_ECHO, - str_debug_2[] PROGMEM = MSG_DEBUG_INFO, - str_debug_4[] PROGMEM = MSG_DEBUG_ERRORS, - str_debug_8[] PROGMEM = MSG_DEBUG_DRYRUN, - str_debug_16[] PROGMEM = MSG_DEBUG_COMMUNICATION - #if ENABLED(DEBUG_LEVELING_FEATURE) - , str_debug_32[] PROGMEM = MSG_DEBUG_LEVELING - #endif - ; - - const static char* const debug_strings[] PROGMEM = { - str_debug_1, str_debug_2, str_debug_4, str_debug_8, str_debug_16 - #if ENABLED(DEBUG_LEVELING_FEATURE) - , str_debug_32 - #endif - }; - - SERIAL_ECHO_START(); - SERIAL_ECHOPGM(MSG_DEBUG_PREFIX); - if (marlin_debug_flags) { - uint8_t comma = 0; - for (uint8_t i = 0; i < COUNT(debug_strings); i++) { - if (TEST(marlin_debug_flags, i)) { - if (comma++) SERIAL_CHAR(','); - serialprintPGM((char*)pgm_read_word(&debug_strings[i])); - } - } - } - else { - SERIAL_ECHOPGM(MSG_DEBUG_OFF); - } - SERIAL_EOL(); -} - -#if ENABLED(HOST_KEEPALIVE_FEATURE) - - /** - * M113: Get or set Host Keepalive interval (0 to disable) - * - * S Optional. Set the keepalive interval. - */ - inline void gcode_M113() { - if (parser.seenval('S')) { - host_keepalive_interval = parser.value_byte(); - NOMORE(host_keepalive_interval, 60); - } - else { - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR("M113 S", (unsigned long)host_keepalive_interval); - } - } - -#endif - -#if ENABLED(BARICUDA) - - #if HAS_HEATER_1 - /** - * M126: Heater 1 valve open - */ - inline void gcode_M126() { baricuda_valve_pressure = parser.byteval('S', 255); } - /** - * M127: Heater 1 valve close - */ - inline void gcode_M127() { baricuda_valve_pressure = 0; } - #endif - - #if HAS_HEATER_2 - /** - * M128: Heater 2 valve open - */ - inline void gcode_M128() { baricuda_e_to_p_pressure = parser.byteval('S', 255); } - /** - * M129: Heater 2 valve close - */ - inline void gcode_M129() { baricuda_e_to_p_pressure = 0; } - #endif - -#endif // BARICUDA - -/** - * M140: Set bed temperature - */ -inline void gcode_M140() { - if (DEBUGGING(DRYRUN)) return; - if (parser.seenval('S')) thermalManager.setTargetBed(parser.value_celsius()); -} - -#if ENABLED(ULTIPANEL) - - /** - * M145: Set the heatup state for a material in the LCD menu - * - * S (0=PLA, 1=ABS) - * H - * B - * F - */ - inline void gcode_M145() { - const uint8_t material = (uint8_t)parser.intval('S'); - if (material >= COUNT(lcd_preheat_hotend_temp)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_MATERIAL_INDEX); - } - else { - int v; - if (parser.seenval('H')) { - v = parser.value_int(); - lcd_preheat_hotend_temp[material] = constrain(v, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - 15); - } - if (parser.seenval('F')) { - v = parser.value_int(); - lcd_preheat_fan_speed[material] = constrain(v, 0, 255); - } - #if TEMP_SENSOR_BED != 0 - if (parser.seenval('B')) { - v = parser.value_int(); - lcd_preheat_bed_temp[material] = constrain(v, BED_MINTEMP, BED_MAXTEMP - 15); - } - #endif - } - } - -#endif // ULTIPANEL - -#if ENABLED(TEMPERATURE_UNITS_SUPPORT) - /** - * M149: Set temperature units - */ - inline void gcode_M149() { - if (parser.seenval('C')) parser.set_input_temp_units(TEMPUNIT_C); - else if (parser.seenval('K')) parser.set_input_temp_units(TEMPUNIT_K); - else if (parser.seenval('F')) parser.set_input_temp_units(TEMPUNIT_F); - } -#endif - -#if HAS_POWER_SWITCH - - /** - * M80 : Turn on the Power Supply - * M80 S : Report the current state and exit - */ - inline void gcode_M80() { - - // S: Report the current power supply state and exit - if (parser.seen('S')) { - serialprintPGM(powersupply_on ? PSTR("PS:1\n") : PSTR("PS:0\n")); - return; - } - - OUT_WRITE(PS_ON_PIN, PS_ON_AWAKE); // GND - - /** - * If you have a switch on suicide pin, this is useful - * if you want to start another print with suicide feature after - * a print without suicide... - */ - #if HAS_SUICIDE - OUT_WRITE(SUICIDE_PIN, HIGH); - #endif - - #if ENABLED(HAVE_TMC2130) - delay(100); - tmc2130_init(); // Settings only stick when the driver has power - #endif - - powersupply_on = true; - - #if ENABLED(ULTIPANEL) - LCD_MESSAGEPGM(WELCOME_MSG); - #endif - } - -#endif // HAS_POWER_SWITCH - -/** - * M81: Turn off Power, including Power Supply, if there is one. - * - * This code should ALWAYS be available for EMERGENCY SHUTDOWN! - */ -inline void gcode_M81() { - thermalManager.disable_all_heaters(); - stepper.finish_and_disable(); - - #if FAN_COUNT > 0 - for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0; - #if ENABLED(PROBING_FANS_OFF) - fans_paused = false; - ZERO(paused_fanSpeeds); - #endif - #endif - - safe_delay(1000); // Wait 1 second before switching off - - #if HAS_SUICIDE - stepper.synchronize(); - suicide(); - #elif HAS_POWER_SWITCH - OUT_WRITE(PS_ON_PIN, PS_ON_ASLEEP); - powersupply_on = false; - #endif - - #if ENABLED(ULTIPANEL) - LCD_MESSAGEPGM(MACHINE_NAME " " MSG_OFF "."); - #endif -} - -/** - * M82: Set E codes absolute (default) - */ -inline void gcode_M82() { axis_relative_modes[E_AXIS] = false; } - -/** - * M83: Set E codes relative while in Absolute Coordinates (G90) mode - */ -inline void gcode_M83() { axis_relative_modes[E_AXIS] = true; } - -/** - * M18, M84: Disable stepper motors - */ -inline void gcode_M18_M84() { - if (parser.seenval('S')) { - stepper_inactive_time = parser.value_millis_from_seconds(); - } - else { - bool all_axis = !((parser.seen('X')) || (parser.seen('Y')) || (parser.seen('Z')) || (parser.seen('E'))); - if (all_axis) { - stepper.finish_and_disable(); - } - else { - stepper.synchronize(); - if (parser.seen('X')) disable_X(); - if (parser.seen('Y')) disable_Y(); - if (parser.seen('Z')) disable_Z(); - #if E0_ENABLE_PIN != X_ENABLE_PIN && E1_ENABLE_PIN != Y_ENABLE_PIN // Only enable on boards that have separate ENABLE_PINS - if (parser.seen('E')) disable_e_steppers(); - #endif - } - - #if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(ULTRA_LCD) // Only needed with an LCD - ubl_lcd_map_control = defer_return_to_status = false; - #endif - } -} - -/** - * M85: Set inactivity shutdown timer with parameter S. To disable set zero (default) - */ -inline void gcode_M85() { - if (parser.seen('S')) max_inactive_time = parser.value_millis_from_seconds(); -} - -/** - * Multi-stepper support for M92, M201, M203 - */ -#if ENABLED(DISTINCT_E_FACTORS) - #define GET_TARGET_EXTRUDER(CMD) if (get_target_extruder_from_command(CMD)) return - #define TARGET_EXTRUDER target_extruder -#else - #define GET_TARGET_EXTRUDER(CMD) NOOP - #define TARGET_EXTRUDER 0 -#endif - -/** - * M92: Set axis steps-per-unit for one or more axes, X, Y, Z, and E. - * (Follows the same syntax as G92) - * - * With multiple extruders use T to specify which one. - */ -inline void gcode_M92() { - - GET_TARGET_EXTRUDER(92); - - LOOP_XYZE(i) { - if (parser.seen(axis_codes[i])) { - if (i == E_AXIS) { - const float value = parser.value_per_axis_unit((AxisEnum)(E_AXIS + TARGET_EXTRUDER)); - if (value < 20.0) { - float factor = planner.axis_steps_per_mm[E_AXIS + TARGET_EXTRUDER] / value; // increase e constants if M92 E14 is given for netfab. - planner.max_jerk[E_AXIS] *= factor; - planner.max_feedrate_mm_s[E_AXIS + TARGET_EXTRUDER] *= factor; - planner.max_acceleration_steps_per_s2[E_AXIS + TARGET_EXTRUDER] *= factor; - } - planner.axis_steps_per_mm[E_AXIS + TARGET_EXTRUDER] = value; - } - else { - planner.axis_steps_per_mm[i] = parser.value_per_axis_unit((AxisEnum)i); - } - } - } - planner.refresh_positioning(); -} - -/** - * Output the current position to serial - */ -void report_current_position() { - SERIAL_PROTOCOLPGM("X:"); - SERIAL_PROTOCOL(current_position[X_AXIS]); - SERIAL_PROTOCOLPGM(" Y:"); - SERIAL_PROTOCOL(current_position[Y_AXIS]); - SERIAL_PROTOCOLPGM(" Z:"); - SERIAL_PROTOCOL(current_position[Z_AXIS]); - SERIAL_PROTOCOLPGM(" E:"); - SERIAL_PROTOCOL(current_position[E_AXIS]); - - stepper.report_positions(); - - #if IS_SCARA - SERIAL_PROTOCOLPAIR("SCARA Theta:", stepper.get_axis_position_degrees(A_AXIS)); - SERIAL_PROTOCOLLNPAIR(" Psi+Theta:", stepper.get_axis_position_degrees(B_AXIS)); - SERIAL_EOL(); - #endif -} - -#ifdef M114_DETAIL - - void report_xyze(const float pos[XYZE], const uint8_t n = 4, const uint8_t precision = 3) { - char str[12]; - for (uint8_t i = 0; i < n; i++) { - SERIAL_CHAR(' '); - SERIAL_CHAR(axis_codes[i]); - SERIAL_CHAR(':'); - SERIAL_PROTOCOL(dtostrf(pos[i], 8, precision, str)); - } - SERIAL_EOL(); - } - - inline void report_xyz(const float pos[XYZ]) { report_xyze(pos, 3); } - - void report_current_position_detail() { - - stepper.synchronize(); - - SERIAL_PROTOCOLPGM("\nLogical:"); - report_xyze(current_position); - - SERIAL_PROTOCOLPGM("Raw: "); - const float raw[XYZ] = { RAW_X_POSITION(current_position[X_AXIS]), RAW_Y_POSITION(current_position[Y_AXIS]), RAW_Z_POSITION(current_position[Z_AXIS]) }; - report_xyz(raw); - - SERIAL_PROTOCOLPGM("Leveled:"); - float leveled[XYZ] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] }; - planner.apply_leveling(leveled); - report_xyz(leveled); - - SERIAL_PROTOCOLPGM("UnLevel:"); - float unleveled[XYZ] = { leveled[X_AXIS], leveled[Y_AXIS], leveled[Z_AXIS] }; - planner.unapply_leveling(unleveled); - report_xyz(unleveled); - - #if IS_KINEMATIC - #if IS_SCARA - SERIAL_PROTOCOLPGM("ScaraK: "); - #else - SERIAL_PROTOCOLPGM("DeltaK: "); - #endif - inverse_kinematics(leveled); // writes delta[] - report_xyz(delta); - #endif - - SERIAL_PROTOCOLPGM("Stepper:"); - const float step_count[XYZE] = { stepper.position(X_AXIS), stepper.position(Y_AXIS), stepper.position(Z_AXIS), stepper.position(E_AXIS) }; - report_xyze(step_count, 4, 0); - - #if IS_SCARA - const float deg[XYZ] = { - stepper.get_axis_position_degrees(A_AXIS), - stepper.get_axis_position_degrees(B_AXIS) - }; - SERIAL_PROTOCOLPGM("Degrees:"); - report_xyze(deg, 2); - #endif - - SERIAL_PROTOCOLPGM("FromStp:"); - get_cartesian_from_steppers(); // writes cartes[XYZ] (with forward kinematics) - const float from_steppers[XYZE] = { cartes[X_AXIS], cartes[Y_AXIS], cartes[Z_AXIS], stepper.get_axis_position_mm(E_AXIS) }; - report_xyze(from_steppers); - - const float diff[XYZE] = { - from_steppers[X_AXIS] - leveled[X_AXIS], - from_steppers[Y_AXIS] - leveled[Y_AXIS], - from_steppers[Z_AXIS] - leveled[Z_AXIS], - from_steppers[E_AXIS] - current_position[E_AXIS] - }; - SERIAL_PROTOCOLPGM("Differ: "); - report_xyze(diff); - } -#endif // M114_DETAIL - -/** - * M114: Report current position to host - */ -inline void gcode_M114() { - - #ifdef M114_DETAIL - if (parser.seen('D')) { - report_current_position_detail(); - return; - } - #endif - - stepper.synchronize(); - report_current_position(); -} - -/** - * M115: Capabilities string - */ -inline void gcode_M115() { - SERIAL_PROTOCOLLNPGM(MSG_M115_REPORT); - - #if ENABLED(EXTENDED_CAPABILITIES_REPORT) - - // EEPROM (M500, M501) - #if ENABLED(EEPROM_SETTINGS) - SERIAL_PROTOCOLLNPGM("Cap:EEPROM:1"); - #else - SERIAL_PROTOCOLLNPGM("Cap:EEPROM:0"); - #endif - - // AUTOREPORT_TEMP (M155) - #if ENABLED(AUTO_REPORT_TEMPERATURES) - SERIAL_PROTOCOLLNPGM("Cap:AUTOREPORT_TEMP:1"); - #else - SERIAL_PROTOCOLLNPGM("Cap:AUTOREPORT_TEMP:0"); - #endif - - // PROGRESS (M530 S L, M531 , M532 X L) - SERIAL_PROTOCOLLNPGM("Cap:PROGRESS:0"); - - // Print Job timer M75, M76, M77 - SERIAL_PROTOCOLLNPGM("Cap:PRINT_JOB:1"); - - // AUTOLEVEL (G29) - #if HAS_ABL - SERIAL_PROTOCOLLNPGM("Cap:AUTOLEVEL:1"); - #else - SERIAL_PROTOCOLLNPGM("Cap:AUTOLEVEL:0"); - #endif - - // Z_PROBE (G30) - #if HAS_BED_PROBE - SERIAL_PROTOCOLLNPGM("Cap:Z_PROBE:1"); - #else - SERIAL_PROTOCOLLNPGM("Cap:Z_PROBE:0"); - #endif - - // MESH_REPORT (M420 V) - #if HAS_LEVELING - SERIAL_PROTOCOLLNPGM("Cap:LEVELING_DATA:1"); - #else - SERIAL_PROTOCOLLNPGM("Cap:LEVELING_DATA:0"); - #endif - - // SOFTWARE_POWER (M80, M81) - #if HAS_POWER_SWITCH - SERIAL_PROTOCOLLNPGM("Cap:SOFTWARE_POWER:1"); - #else - SERIAL_PROTOCOLLNPGM("Cap:SOFTWARE_POWER:0"); - #endif - - // CASE LIGHTS (M355) - #if HAS_CASE_LIGHT - SERIAL_PROTOCOLLNPGM("Cap:TOGGLE_LIGHTS:1"); - if (USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) { - SERIAL_PROTOCOLLNPGM("Cap:CASE_LIGHT_BRIGHTNESS:1"); - } - else - SERIAL_PROTOCOLLNPGM("Cap:CASE_LIGHT_BRIGHTNESS:0"); - #else - SERIAL_PROTOCOLLNPGM("Cap:TOGGLE_LIGHTS:0"); - SERIAL_PROTOCOLLNPGM("Cap:CASE_LIGHT_BRIGHTNESS:0"); - #endif - - // EMERGENCY_PARSER (M108, M112, M410) - #if ENABLED(EMERGENCY_PARSER) - SERIAL_PROTOCOLLNPGM("Cap:EMERGENCY_PARSER:1"); - #else - SERIAL_PROTOCOLLNPGM("Cap:EMERGENCY_PARSER:0"); - #endif - - #endif // EXTENDED_CAPABILITIES_REPORT -} - -/** - * M117: Set LCD Status Message - */ -inline void gcode_M117() { lcd_setstatus(parser.string_arg); } - -/** - * M118: Display a message in the host console. - * - * A Append '// ' for an action command, as in OctoPrint - * E Have the host 'echo:' the text - */ -inline void gcode_M118() { - if (parser.boolval('E')) SERIAL_ECHO_START(); - if (parser.boolval('A')) SERIAL_ECHOPGM("// "); - SERIAL_ECHOLN(parser.string_arg); -} - -/** - * M119: Output endstop states to serial output - */ -inline void gcode_M119() { endstops.M119(); } - -/** - * M120: Enable endstops and set non-homing endstop state to "enabled" - */ -inline void gcode_M120() { endstops.enable_globally(true); } - -/** - * M121: Disable endstops and set non-homing endstop state to "disabled" - */ -inline void gcode_M121() { endstops.enable_globally(false); } - -#if ENABLED(PARK_HEAD_ON_PAUSE) - - /** - * M125: Store current position and move to filament change position. - * Called on pause (by M25) to prevent material leaking onto the - * object. On resume (M24) the head will be moved back and the - * print will resume. - * - * If Marlin is compiled without SD Card support, M125 can be - * used directly to pause the print and move to park position, - * resuming with a button click or M108. - * - * L = override retract length - * X = override X - * Y = override Y - * Z = override Z raise - */ - inline void gcode_M125() { - - // Initial retract before move to filament change position - const float retract = parser.seen('L') ? parser.value_axis_units(E_AXIS) : 0 - #ifdef PAUSE_PARK_RETRACT_LENGTH - - (PAUSE_PARK_RETRACT_LENGTH) - #endif - ; - - // Lift Z axis - const float z_lift = parser.linearval('Z') - #ifdef PAUSE_PARK_Z_ADD - + PAUSE_PARK_Z_ADD - #endif - ; - - // Move XY axes to filament change position or given position - const float x_pos = parser.linearval('X') - #ifdef PAUSE_PARK_X_POS - + PAUSE_PARK_X_POS - #endif - #if HOTENDS > 1 && DISABLED(DUAL_X_CARRIAGE) - + (active_extruder ? hotend_offset[X_AXIS][active_extruder] : 0) - #endif - ; - const float y_pos = parser.linearval('Y') - #ifdef PAUSE_PARK_Y_POS - + PAUSE_PARK_Y_POS - #endif - #if HOTENDS > 1 && DISABLED(DUAL_X_CARRIAGE) - + (active_extruder ? hotend_offset[Y_AXIS][active_extruder] : 0) - #endif - ; - - #if DISABLED(SDSUPPORT) - const bool job_running = print_job_timer.isRunning(); - #endif - - if (pause_print(retract, z_lift, x_pos, y_pos)) { - #if DISABLED(SDSUPPORT) - // Wait for lcd click or M108 - wait_for_filament_reload(); - - // Return to print position and continue - resume_print(); - - if (job_running) print_job_timer.start(); - #endif - } - } - -#endif // PARK_HEAD_ON_PAUSE - -#if HAS_COLOR_LEDS - - /** - * M150: Set Status LED Color - Use R-U-B-W for R-G-B-W - * - * Always sets all 3 or 4 components. If a component is left out, set to 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 - * - */ - inline void gcode_M150() { - set_led_color( - 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 - #if ENABLED(RGBW_LED) || ENABLED(NEOPIXEL_RGBW_LED) - , parser.seen('W') ? (parser.has_value() ? parser.value_byte() : 255) : 0 - #endif - ); - } - -#endif // HAS_COLOR_LEDS - -/** - * M200: Set filament diameter and set E axis units to cubic units - * - * T - Optional extruder number. Current extruder if omitted. - * D - Diameter of the filament. Use "D0" to switch back to linear units on the E axis. - */ -inline void gcode_M200() { - - if (get_target_extruder_from_command(200)) return; - - if (parser.seen('D')) { - // setting any extruder filament size disables volumetric on the assumption that - // slicers either generate in extruder values as cubic mm or as as filament feeds - // for all extruders - volumetric_enabled = (parser.value_linear_units() != 0.0); - if (volumetric_enabled) { - filament_size[target_extruder] = parser.value_linear_units(); - // make sure all extruders have some sane value for the filament size - for (uint8_t i = 0; i < COUNT(filament_size); i++) - if (! filament_size[i]) filament_size[i] = DEFAULT_NOMINAL_FILAMENT_DIA; - } - } - calculate_volumetric_multipliers(); -} - -/** - * M201: Set max acceleration in units/s^2 for print moves (M201 X1000 Y1000) - * - * With multiple extruders use T to specify which one. - */ -inline void gcode_M201() { - - GET_TARGET_EXTRUDER(201); - - LOOP_XYZE(i) { - if (parser.seen(axis_codes[i])) { - const uint8_t a = i + (i == E_AXIS ? TARGET_EXTRUDER : 0); - planner.max_acceleration_mm_per_s2[a] = parser.value_axis_units((AxisEnum)a); - } - } - // steps per sq second need to be updated to agree with the units per sq second (as they are what is used in the planner) - planner.reset_acceleration_rates(); -} - -#if 0 // Not used for Sprinter/grbl gen6 - inline void gcode_M202() { - LOOP_XYZE(i) { - if (parser.seen(axis_codes[i])) axis_travel_steps_per_sqr_second[i] = parser.value_axis_units((AxisEnum)i) * planner.axis_steps_per_mm[i]; - } - } -#endif - - -/** - * M203: Set maximum feedrate that your machine can sustain (M203 X200 Y200 Z300 E10000) in units/sec - * - * With multiple extruders use T to specify which one. - */ -inline void gcode_M203() { - - GET_TARGET_EXTRUDER(203); - - LOOP_XYZE(i) - if (parser.seen(axis_codes[i])) { - const uint8_t a = i + (i == E_AXIS ? TARGET_EXTRUDER : 0); - planner.max_feedrate_mm_s[a] = parser.value_axis_units((AxisEnum)a); - } -} - -/** - * M204: Set Accelerations in units/sec^2 (M204 P1200 R3000 T3000) - * - * P = Printing moves - * R = Retract only (no X, Y, Z) moves - * T = Travel (non printing) moves - * - * Also sets minimum segment time in ms (B20000) to prevent buffer under-runs and M20 minimum feedrate - */ -inline void gcode_M204() { - if (parser.seen('S')) { // Kept for legacy compatibility. Should NOT BE USED for new developments. - planner.travel_acceleration = planner.acceleration = parser.value_linear_units(); - SERIAL_ECHOLNPAIR("Setting Print and Travel Acceleration: ", planner.acceleration); - } - if (parser.seen('P')) { - planner.acceleration = parser.value_linear_units(); - SERIAL_ECHOLNPAIR("Setting Print Acceleration: ", planner.acceleration); - } - if (parser.seen('R')) { - planner.retract_acceleration = parser.value_linear_units(); - SERIAL_ECHOLNPAIR("Setting Retract Acceleration: ", planner.retract_acceleration); - } - if (parser.seen('T')) { - planner.travel_acceleration = parser.value_linear_units(); - SERIAL_ECHOLNPAIR("Setting Travel Acceleration: ", planner.travel_acceleration); - } -} - -/** - * M205: Set Advanced Settings - * - * S = Min Feed Rate (units/s) - * T = Min Travel Feed Rate (units/s) - * B = Min Segment Time (µs) - * X = Max X Jerk (units/sec^2) - * Y = Max Y Jerk (units/sec^2) - * Z = Max Z Jerk (units/sec^2) - * E = Max E Jerk (units/sec^2) - */ -inline void gcode_M205() { - if (parser.seen('S')) planner.min_feedrate_mm_s = parser.value_linear_units(); - if (parser.seen('T')) planner.min_travel_feedrate_mm_s = parser.value_linear_units(); - if (parser.seen('B')) planner.min_segment_time = parser.value_millis(); - if (parser.seen('X')) planner.max_jerk[X_AXIS] = parser.value_linear_units(); - if (parser.seen('Y')) planner.max_jerk[Y_AXIS] = parser.value_linear_units(); - if (parser.seen('Z')) planner.max_jerk[Z_AXIS] = parser.value_linear_units(); - if (parser.seen('E')) planner.max_jerk[E_AXIS] = parser.value_linear_units(); -} - -#if HAS_M206_COMMAND - - /** - * M206: Set Additional Homing Offset (X Y Z). SCARA aliases T=X, P=Y - * - * *** @thinkyhead: I recommend deprecating M206 for SCARA in favor of M665. - * *** M206 for SCARA will remain enabled in 1.1.x for compatibility. - * *** In the 2.0 release, it will simply be disabled by default. - */ - inline void gcode_M206() { - LOOP_XYZ(i) - if (parser.seen(axis_codes[i])) - set_home_offset((AxisEnum)i, parser.value_linear_units()); - - #if ENABLED(MORGAN_SCARA) - if (parser.seen('T')) set_home_offset(A_AXIS, parser.value_linear_units()); // Theta - if (parser.seen('P')) set_home_offset(B_AXIS, parser.value_linear_units()); // Psi - #endif - - SYNC_PLAN_POSITION_KINEMATIC(); - report_current_position(); - } - -#endif // HAS_M206_COMMAND - -#if ENABLED(DELTA) - /** - * M665: Set delta configurations - * - * H = delta height - * L = diagonal rod - * R = delta radius - * S = segments per second - * B = delta calibration radius - * X = Alpha (Tower 1) angle trim - * Y = Beta (Tower 2) angle trim - * Z = Rotate A and B by this angle - */ - inline void gcode_M665() { - if (parser.seen('H')) { - home_offset[Z_AXIS] = parser.value_linear_units() - DELTA_HEIGHT; - update_software_endstops(Z_AXIS); - } - if (parser.seen('L')) delta_diagonal_rod = parser.value_linear_units(); - if (parser.seen('R')) delta_radius = parser.value_linear_units(); - if (parser.seen('S')) delta_segments_per_second = parser.value_float(); - if (parser.seen('B')) delta_calibration_radius = parser.value_float(); - if (parser.seen('X')) delta_tower_angle_trim[A_AXIS] = parser.value_float(); - if (parser.seen('Y')) delta_tower_angle_trim[B_AXIS] = parser.value_float(); - if (parser.seen('Z')) { // rotate all 3 axis for Z = 0 - delta_tower_angle_trim[A_AXIS] -= parser.value_float(); - delta_tower_angle_trim[B_AXIS] -= parser.value_float(); - } - recalc_delta_settings(delta_radius, delta_diagonal_rod); - } - /** - * M666: Set delta endstop adjustment - */ - inline void gcode_M666() { - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPGM(">>> gcode_M666"); - } - #endif - LOOP_XYZ(i) { - if (parser.seen(axis_codes[i])) { - endstop_adj[i] = parser.value_linear_units(); - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOPAIR("endstop_adj[", axis_codes[i]); - SERIAL_ECHOLNPAIR("] = ", endstop_adj[i]); - } - #endif - } - } - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPGM("<<< gcode_M666"); - } - #endif - // normalize endstops so all are <=0; set the residue to delta height - const float z_temp = MAX3(endstop_adj[A_AXIS], endstop_adj[B_AXIS], endstop_adj[C_AXIS]); - home_offset[Z_AXIS] -= z_temp; - LOOP_XYZ(i) endstop_adj[i] -= z_temp; - } - -#elif IS_SCARA - - /** - * M665: Set SCARA settings - * - * Parameters: - * - * S[segments-per-second] - Segments-per-second - * P[theta-psi-offset] - Theta-Psi offset, added to the shoulder (A/X) angle - * T[theta-offset] - Theta offset, added to the elbow (B/Y) angle - * - * A, P, and X are all aliases for the shoulder angle - * B, T, and Y are all aliases for the elbow angle - */ - inline void gcode_M665() { - if (parser.seen('S')) delta_segments_per_second = parser.value_float(); - - const bool hasA = parser.seen('A'), hasP = parser.seen('P'), hasX = parser.seen('X'); - const uint8_t sumAPX = hasA + hasP + hasX; - if (sumAPX == 1) - home_offset[A_AXIS] = parser.value_float(); - else if (sumAPX > 1) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM("Only one of A, P, or X is allowed."); - return; - } - - const bool hasB = parser.seen('B'), hasT = parser.seen('T'), hasY = parser.seen('Y'); - const uint8_t sumBTY = hasB + hasT + hasY; - if (sumBTY == 1) - home_offset[B_AXIS] = parser.value_float(); - else if (sumBTY > 1) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM("Only one of B, T, or Y is allowed."); - return; - } - } - -#elif ENABLED(Z_DUAL_ENDSTOPS) // !DELTA && ENABLED(Z_DUAL_ENDSTOPS) - - /** - * M666: For Z Dual Endstop setup, set z axis offset to the z2 axis. - */ - inline void gcode_M666() { - if (parser.seen('Z')) z_endstop_adj = parser.value_linear_units(); - SERIAL_ECHOLNPAIR("Z Endstop Adjustment set to (mm):", z_endstop_adj); - } - -#endif // !DELTA && Z_DUAL_ENDSTOPS - -#if ENABLED(FWRETRACT) - - /** - * M207: Set firmware retraction values - * - * S[+units] retract_length - * W[+units] swap_retract_length (multi-extruder) - * F[units/min] retract_feedrate_mm_s - * Z[units] retract_zlift - */ - inline void gcode_M207() { - if (parser.seen('S')) retract_length = parser.value_axis_units(E_AXIS); - if (parser.seen('F')) retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); - if (parser.seen('Z')) retract_zlift = parser.value_linear_units(); - if (parser.seen('W')) swap_retract_length = parser.value_axis_units(E_AXIS); - } - - /** - * M208: Set firmware un-retraction values - * - * S[+units] retract_recover_length (in addition to M207 S*) - * W[+units] swap_retract_recover_length (multi-extruder) - * F[units/min] retract_recover_feedrate_mm_s - * R[units/min] swap_retract_recover_feedrate_mm_s - */ - inline void gcode_M208() { - if (parser.seen('S')) retract_recover_length = parser.value_axis_units(E_AXIS); - if (parser.seen('F')) retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); - if (parser.seen('R')) swap_retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); - if (parser.seen('W')) swap_retract_recover_length = parser.value_axis_units(E_AXIS); - } - - /** - * M209: Enable automatic retract (M209 S1) - * For slicers that don't support G10/11, reversed extrude-only - * moves will be classified as retraction. - */ - inline void gcode_M209() { - if (MIN_AUTORETRACT <= MAX_AUTORETRACT) { - if (parser.seen('S')) { - autoretract_enabled = parser.value_bool(); - for (uint8_t i = 0; i < EXTRUDERS; i++) retracted[i] = false; - } - } - } - -#endif // FWRETRACT - -/** - * M211: Enable, Disable, and/or Report software endstops - * - * Usage: M211 S1 to enable, M211 S0 to disable, M211 alone for report - */ -inline void gcode_M211() { - SERIAL_ECHO_START(); - #if HAS_SOFTWARE_ENDSTOPS - if (parser.seen('S')) soft_endstops_enabled = parser.value_bool(); - SERIAL_ECHOPGM(MSG_SOFT_ENDSTOPS); - serialprintPGM(soft_endstops_enabled ? PSTR(MSG_ON) : PSTR(MSG_OFF)); - #else - SERIAL_ECHOPGM(MSG_SOFT_ENDSTOPS); - SERIAL_ECHOPGM(MSG_OFF); - #endif - SERIAL_ECHOPGM(MSG_SOFT_MIN); - SERIAL_ECHOPAIR( MSG_X, soft_endstop_min[X_AXIS]); - SERIAL_ECHOPAIR(" " MSG_Y, soft_endstop_min[Y_AXIS]); - SERIAL_ECHOPAIR(" " MSG_Z, soft_endstop_min[Z_AXIS]); - SERIAL_ECHOPGM(MSG_SOFT_MAX); - SERIAL_ECHOPAIR( MSG_X, soft_endstop_max[X_AXIS]); - SERIAL_ECHOPAIR(" " MSG_Y, soft_endstop_max[Y_AXIS]); - SERIAL_ECHOLNPAIR(" " MSG_Z, soft_endstop_max[Z_AXIS]); -} - -#if HOTENDS > 1 - - /** - * M218 - set hotend offset (in linear units) - * - * T - * X - * Y - * Z - Available with DUAL_X_CARRIAGE and SWITCHING_NOZZLE - */ - inline void gcode_M218() { - if (get_target_extruder_from_command(218) || target_extruder == 0) return; - - if (parser.seenval('X')) hotend_offset[X_AXIS][target_extruder] = parser.value_linear_units(); - if (parser.seenval('Y')) hotend_offset[Y_AXIS][target_extruder] = parser.value_linear_units(); - - #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_NOZZLE) || ENABLED(PARKING_EXTRUDER) - if (parser.seenval('Z')) hotend_offset[Z_AXIS][target_extruder] = parser.value_linear_units(); - #endif - - SERIAL_ECHO_START(); - SERIAL_ECHOPGM(MSG_HOTEND_OFFSET); - HOTEND_LOOP() { - SERIAL_CHAR(' '); - SERIAL_ECHO(hotend_offset[X_AXIS][e]); - SERIAL_CHAR(','); - SERIAL_ECHO(hotend_offset[Y_AXIS][e]); - #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_NOZZLE) || ENABLED(PARKING_EXTRUDER) - SERIAL_CHAR(','); - SERIAL_ECHO(hotend_offset[Z_AXIS][e]); - #endif - } - SERIAL_EOL(); - } - -#endif // HOTENDS > 1 - -/** - * M220: Set speed percentage factor, aka "Feed Rate" (M220 S95) - */ -inline void gcode_M220() { - if (parser.seenval('S')) feedrate_percentage = parser.value_int(); -} - -/** - * M221: Set extrusion percentage (M221 T0 S95) - */ -inline void gcode_M221() { - if (get_target_extruder_from_command(221)) return; - if (parser.seenval('S')) - flow_percentage[target_extruder] = parser.value_int(); -} - -/** - * M226: Wait until the specified pin reaches the state required (M226 P S) - */ -inline void gcode_M226() { - if (parser.seen('P')) { - const int pin_number = parser.value_int(), - pin_state = parser.intval('S', -1); // required pin state - default is inverted - - if (WITHIN(pin_state, -1, 1) && pin_number > -1 && !pin_is_protected(pin_number)) { - - int target = LOW; - - stepper.synchronize(); - - pinMode(pin_number, INPUT); - switch (pin_state) { - case 1: - target = HIGH; - break; - case 0: - target = LOW; - break; - case -1: - target = !digitalRead(pin_number); - break; - } - - while (digitalRead(pin_number) != target) idle(); - - } // pin_state -1 0 1 && pin_number > -1 - } // parser.seen('P') -} - -#if ENABLED(EXPERIMENTAL_I2CBUS) - - /** - * M260: Send data to a I2C slave device - * - * This is a PoC, the formating and arguments for the GCODE will - * change to be more compatible, the current proposal is: - * - * M260 A ; Sets the I2C slave address the data will be sent to - * - * M260 B - * M260 B - * M260 B - * - * M260 S1 ; Send the buffered data and reset the buffer - * M260 R1 ; Reset the buffer without sending data - * - */ - inline void gcode_M260() { - // Set the target address - if (parser.seen('A')) i2c.address(parser.value_byte()); - - // Add a new byte to the buffer - if (parser.seen('B')) i2c.addbyte(parser.value_byte()); - - // Flush the buffer to the bus - if (parser.seen('S')) i2c.send(); - - // Reset and rewind the buffer - else if (parser.seen('R')) i2c.reset(); - } - - /** - * M261: Request X bytes from I2C slave device - * - * Usage: M261 A B - */ - inline void gcode_M261() { - if (parser.seen('A')) i2c.address(parser.value_byte()); - - uint8_t bytes = parser.byteval('B', 1); - - if (i2c.addr && bytes && bytes <= TWIBUS_BUFFER_SIZE) { - i2c.relay(bytes); - } - else { - SERIAL_ERROR_START(); - SERIAL_ERRORLN("Bad i2c request"); - } - } - -#endif // EXPERIMENTAL_I2CBUS - -#if HAS_SERVOS - - /** - * M280: Get or set servo position. P [S] - */ - inline void gcode_M280() { - if (!parser.seen('P')) return; - const int servo_index = parser.value_int(); - if (WITHIN(servo_index, 0, NUM_SERVOS - 1)) { - if (parser.seen('S')) - MOVE_SERVO(servo_index, parser.value_int()); - else { - SERIAL_ECHO_START(); - SERIAL_ECHOPAIR(" Servo ", servo_index); - SERIAL_ECHOLNPAIR(": ", servo[servo_index].read()); - } - } - else { - SERIAL_ERROR_START(); - SERIAL_ECHOPAIR("Servo ", servo_index); - SERIAL_ECHOLNPGM(" out of range"); - } - } - -#endif // HAS_SERVOS - -#if HAS_BUZZER - - /** - * M300: Play beep sound S P - */ - inline void gcode_M300() { - uint16_t const frequency = parser.ushortval('S', 260); - uint16_t duration = parser.ushortval('P', 1000); - - // Limits the tone duration to 0-5 seconds. - NOMORE(duration, 5000); - - BUZZ(duration, frequency); - } - -#endif // HAS_BUZZER - -#if ENABLED(PIDTEMP) - - /** - * M301: Set PID parameters P I D (and optionally C, L) - * - * P[float] Kp term - * I[float] Ki term (unscaled) - * D[float] Kd term (unscaled) - * - * With PID_EXTRUSION_SCALING: - * - * C[float] Kc term - * L[float] LPQ length - */ - inline void gcode_M301() { - - // multi-extruder PID patch: M301 updates or prints a single extruder's PID values - // default behaviour (omitting E parameter) is to update for extruder 0 only - const uint8_t e = parser.byteval('E'); // extruder being updated - - if (e < HOTENDS) { // catch bad input value - if (parser.seen('P')) PID_PARAM(Kp, e) = parser.value_float(); - if (parser.seen('I')) PID_PARAM(Ki, e) = scalePID_i(parser.value_float()); - if (parser.seen('D')) PID_PARAM(Kd, e) = scalePID_d(parser.value_float()); - #if ENABLED(PID_EXTRUSION_SCALING) - if (parser.seen('C')) PID_PARAM(Kc, e) = parser.value_float(); - if (parser.seen('L')) lpq_len = parser.value_float(); - NOMORE(lpq_len, LPQ_MAX_LEN); - #endif - - thermalManager.updatePID(); - SERIAL_ECHO_START(); - #if ENABLED(PID_PARAMS_PER_HOTEND) - SERIAL_ECHOPAIR(" e:", e); // specify extruder in serial output - #endif // PID_PARAMS_PER_HOTEND - SERIAL_ECHOPAIR(" p:", PID_PARAM(Kp, e)); - SERIAL_ECHOPAIR(" i:", unscalePID_i(PID_PARAM(Ki, e))); - SERIAL_ECHOPAIR(" d:", unscalePID_d(PID_PARAM(Kd, e))); - #if ENABLED(PID_EXTRUSION_SCALING) - //Kc does not have scaling applied above, or in resetting defaults - SERIAL_ECHOPAIR(" c:", PID_PARAM(Kc, e)); - #endif - SERIAL_EOL(); - } - else { - SERIAL_ERROR_START(); - SERIAL_ERRORLN(MSG_INVALID_EXTRUDER); - } - } - -#endif // PIDTEMP - -#if ENABLED(PIDTEMPBED) - - inline void gcode_M304() { - if (parser.seen('P')) thermalManager.bedKp = parser.value_float(); - if (parser.seen('I')) thermalManager.bedKi = scalePID_i(parser.value_float()); - if (parser.seen('D')) thermalManager.bedKd = scalePID_d(parser.value_float()); - - thermalManager.updatePID(); - - SERIAL_ECHO_START(); - SERIAL_ECHOPAIR(" p:", thermalManager.bedKp); - SERIAL_ECHOPAIR(" i:", unscalePID_i(thermalManager.bedKi)); - SERIAL_ECHOLNPAIR(" d:", unscalePID_d(thermalManager.bedKd)); - } - -#endif // PIDTEMPBED - -#if defined(CHDK) || HAS_PHOTOGRAPH - - /** - * M240: Trigger a camera by emulating a Canon RC-1 - * See http://www.doc-diy.net/photo/rc-1_hacked/ - */ - inline void gcode_M240() { - #ifdef CHDK - - OUT_WRITE(CHDK, HIGH); - chdkHigh = millis(); - chdkActive = true; - - #elif HAS_PHOTOGRAPH - - const uint8_t NUM_PULSES = 16; - const float PULSE_LENGTH = 0.01524; - for (int i = 0; i < NUM_PULSES; i++) { - WRITE(PHOTOGRAPH_PIN, HIGH); - _delay_ms(PULSE_LENGTH); - WRITE(PHOTOGRAPH_PIN, LOW); - _delay_ms(PULSE_LENGTH); - } - delay(7.33); - for (int i = 0; i < NUM_PULSES; i++) { - WRITE(PHOTOGRAPH_PIN, HIGH); - _delay_ms(PULSE_LENGTH); - WRITE(PHOTOGRAPH_PIN, LOW); - _delay_ms(PULSE_LENGTH); - } - - #endif // !CHDK && HAS_PHOTOGRAPH - } - -#endif // CHDK || PHOTOGRAPH_PIN - -#if HAS_LCD_CONTRAST - - /** - * M250: Read and optionally set the LCD contrast - */ - inline void gcode_M250() { - if (parser.seen('C')) set_lcd_contrast(parser.value_int()); - SERIAL_PROTOCOLPGM("lcd contrast value: "); - SERIAL_PROTOCOL(lcd_contrast); - SERIAL_EOL(); - } - -#endif // HAS_LCD_CONTRAST - -#if ENABLED(PREVENT_COLD_EXTRUSION) - - /** - * M302: Allow cold extrudes, or set the minimum extrude temperature - * - * S sets the minimum extrude temperature - * P enables (1) or disables (0) cold extrusion - * - * Examples: - * - * M302 ; report current cold extrusion state - * M302 P0 ; enable cold extrusion checking - * M302 P1 ; disables cold extrusion checking - * M302 S0 ; always allow extrusion (disables checking) - * M302 S170 ; only allow extrusion above 170 - * M302 S170 P1 ; set min extrude temp to 170 but leave disabled - */ - inline void gcode_M302() { - const bool seen_S = parser.seen('S'); - if (seen_S) { - thermalManager.extrude_min_temp = parser.value_celsius(); - thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0); - } - - if (parser.seen('P')) - thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0) || parser.value_bool(); - else if (!seen_S) { - // Report current state - SERIAL_ECHO_START(); - SERIAL_ECHOPAIR("Cold extrudes are ", (thermalManager.allow_cold_extrude ? "en" : "dis")); - SERIAL_ECHOPAIR("abled (min temp ", thermalManager.extrude_min_temp); - SERIAL_ECHOLNPGM("C)"); - } - } - -#endif // PREVENT_COLD_EXTRUSION - -/** - * M303: PID relay autotune - * - * S sets the target temperature. (default 150C) - * E (-1 for the bed) (default 0) - * C - * U with a non-zero value will apply the result to current settings - */ -inline void gcode_M303() { - #if HAS_PID_HEATING - const int e = parser.intval('E'), c = parser.intval('C', 5); - const bool u = parser.boolval('U'); - - int16_t temp = parser.celsiusval('S', e < 0 ? 70 : 150); - - if (WITHIN(e, 0, HOTENDS - 1)) - target_extruder = e; - - #if DISABLED(BUSY_WHILE_HEATING) - KEEPALIVE_STATE(NOT_BUSY); - #endif - - thermalManager.PID_autotune(temp, e, c, u); - - #if DISABLED(BUSY_WHILE_HEATING) - KEEPALIVE_STATE(IN_HANDLER); - #endif - #else - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_M303_DISABLED); - #endif -} - -#if ENABLED(MORGAN_SCARA) - - bool SCARA_move_to_cal(uint8_t delta_a, uint8_t delta_b) { - if (IsRunning()) { - forward_kinematics_SCARA(delta_a, delta_b); - destination[X_AXIS] = LOGICAL_X_POSITION(cartes[X_AXIS]); - destination[Y_AXIS] = LOGICAL_Y_POSITION(cartes[Y_AXIS]); - destination[Z_AXIS] = current_position[Z_AXIS]; - prepare_move_to_destination(); - return true; - } - return false; - } - - /** - * M360: SCARA calibration: Move to cal-position ThetaA (0 deg calibration) - */ - inline bool gcode_M360() { - SERIAL_ECHOLNPGM(" Cal: Theta 0"); - return SCARA_move_to_cal(0, 120); - } - - /** - * M361: SCARA calibration: Move to cal-position ThetaB (90 deg calibration - steps per degree) - */ - inline bool gcode_M361() { - SERIAL_ECHOLNPGM(" Cal: Theta 90"); - return SCARA_move_to_cal(90, 130); - } - - /** - * M362: SCARA calibration: Move to cal-position PsiA (0 deg calibration) - */ - inline bool gcode_M362() { - SERIAL_ECHOLNPGM(" Cal: Psi 0"); - return SCARA_move_to_cal(60, 180); - } - - /** - * M363: SCARA calibration: Move to cal-position PsiB (90 deg calibration - steps per degree) - */ - inline bool gcode_M363() { - SERIAL_ECHOLNPGM(" Cal: Psi 90"); - return SCARA_move_to_cal(50, 90); - } - - /** - * M364: SCARA calibration: Move to cal-position PsiC (90 deg to Theta calibration position) - */ - inline bool gcode_M364() { - SERIAL_ECHOLNPGM(" Cal: Theta-Psi 90"); - return SCARA_move_to_cal(45, 135); - } - -#endif // SCARA - -#if ENABLED(EXT_SOLENOID) - - void enable_solenoid(const uint8_t num) { - switch (num) { - case 0: - OUT_WRITE(SOL0_PIN, HIGH); - break; - #if HAS_SOLENOID_1 && EXTRUDERS > 1 - case 1: - OUT_WRITE(SOL1_PIN, HIGH); - break; - #endif - #if HAS_SOLENOID_2 && EXTRUDERS > 2 - case 2: - OUT_WRITE(SOL2_PIN, HIGH); - break; - #endif - #if HAS_SOLENOID_3 && EXTRUDERS > 3 - case 3: - OUT_WRITE(SOL3_PIN, HIGH); - break; - #endif - #if HAS_SOLENOID_4 && EXTRUDERS > 4 - case 4: - OUT_WRITE(SOL4_PIN, HIGH); - break; - #endif - default: - SERIAL_ECHO_START(); - SERIAL_ECHOLNPGM(MSG_INVALID_SOLENOID); - break; - } - } - - void enable_solenoid_on_active_extruder() { enable_solenoid(active_extruder); } - - void disable_all_solenoids() { - OUT_WRITE(SOL0_PIN, LOW); - #if HAS_SOLENOID_1 && EXTRUDERS > 1 - OUT_WRITE(SOL1_PIN, LOW); - #endif - #if HAS_SOLENOID_2 && EXTRUDERS > 2 - OUT_WRITE(SOL2_PIN, LOW); - #endif - #if HAS_SOLENOID_3 && EXTRUDERS > 3 - OUT_WRITE(SOL3_PIN, LOW); - #endif - #if HAS_SOLENOID_4 && EXTRUDERS > 4 - OUT_WRITE(SOL4_PIN, LOW); - #endif - } - - /** - * M380: Enable solenoid on the active extruder - */ - inline void gcode_M380() { enable_solenoid_on_active_extruder(); } - - /** - * M381: Disable all solenoids - */ - inline void gcode_M381() { disable_all_solenoids(); } - -#endif // EXT_SOLENOID - -/** - * M400: Finish all moves - */ -inline void gcode_M400() { stepper.synchronize(); } - -#if HAS_BED_PROBE - - /** - * M401: Engage Z Servo endstop if available - */ - inline void gcode_M401() { DEPLOY_PROBE(); } - - /** - * M402: Retract Z Servo endstop if enabled - */ - inline void gcode_M402() { STOW_PROBE(); } - -#endif // HAS_BED_PROBE - -#if ENABLED(FILAMENT_WIDTH_SENSOR) - - /** - * M404: Display or set (in current units) the nominal filament width (3mm, 1.75mm ) W<3.0> - */ - inline void gcode_M404() { - if (parser.seen('W')) { - filament_width_nominal = parser.value_linear_units(); - } - else { - SERIAL_PROTOCOLPGM("Filament dia (nominal mm):"); - SERIAL_PROTOCOLLN(filament_width_nominal); - } - } - - /** - * M405: Turn on filament sensor for control - */ - inline void gcode_M405() { - // This is technically a linear measurement, but since it's quantized to centimeters and is a different - // unit than everything else, it uses parser.value_byte() instead of parser.value_linear_units(). - if (parser.seen('D')) { - meas_delay_cm = parser.value_byte(); - NOMORE(meas_delay_cm, MAX_MEASUREMENT_DELAY); - } - - if (filwidth_delay_index[1] == -1) { // Initialize the ring buffer if not done since startup - const uint8_t temp_ratio = thermalManager.widthFil_to_size_ratio() - 100; // -100 to scale within a signed byte - - for (uint8_t i = 0; i < COUNT(measurement_delay); ++i) - measurement_delay[i] = temp_ratio; - - filwidth_delay_index[0] = filwidth_delay_index[1] = 0; - } - - filament_sensor = true; - - //SERIAL_PROTOCOLPGM("Filament dia (measured mm):"); - //SERIAL_PROTOCOL(filament_width_meas); - //SERIAL_PROTOCOLPGM("Extrusion ratio(%):"); - //SERIAL_PROTOCOL(flow_percentage[active_extruder]); - } - - /** - * M406: Turn off filament sensor for control - */ - inline void gcode_M406() { - filament_sensor = false; - calculate_volumetric_multipliers(); // Restore correct 'volumetric_multiplier' value - } - - /** - * M407: Get measured filament diameter on serial output - */ - inline void gcode_M407() { - SERIAL_PROTOCOLPGM("Filament dia (measured mm):"); - SERIAL_PROTOCOLLN(filament_width_meas); - } - -#endif // FILAMENT_WIDTH_SENSOR - -void quickstop_stepper() { - stepper.quick_stop(); - stepper.synchronize(); - set_current_from_steppers_for_axis(ALL_AXES); - SYNC_PLAN_POSITION_KINEMATIC(); -} - -#if HAS_LEVELING - /** - * M420: Enable/Disable Bed Leveling and/or set the Z fade height. - * - * S[bool] Turns leveling on or off - * Z[height] Sets the Z fade height (0 or none to disable) - * V[bool] Verbose - Print the leveling grid - * - * With AUTO_BED_LEVELING_UBL only: - * - * L[index] Load UBL mesh from index (0 is default) - */ - inline void gcode_M420() { - - #if ENABLED(AUTO_BED_LEVELING_UBL) - - // L to load a mesh from the EEPROM - if (parser.seen('L')) { - - #if ENABLED(EEPROM_SETTINGS) - const int8_t storage_slot = parser.has_value() ? parser.value_int() : ubl.state.storage_slot; - const int16_t a = settings.calc_num_meshes(); - - if (!a) { - SERIAL_PROTOCOLLNPGM("?EEPROM storage not available."); - return; - } - - if (!WITHIN(storage_slot, 0, a - 1)) { - SERIAL_PROTOCOLLNPGM("?Invalid storage slot."); - SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1); - return; - } - - settings.load_mesh(storage_slot); - ubl.state.storage_slot = storage_slot; - - #else - - SERIAL_PROTOCOLLNPGM("?EEPROM storage not available."); - return; - - #endif - } - - // L to load a mesh from the EEPROM - if (parser.seen('L') || parser.seen('V')) { - ubl.display_map(0); // Currently only supports one map type - SERIAL_ECHOLNPAIR("UBL_MESH_VALID = ", UBL_MESH_VALID); - SERIAL_ECHOLNPAIR("ubl.state.storage_slot = ", ubl.state.storage_slot); - } - - #endif // AUTO_BED_LEVELING_UBL - - // V to print the matrix or mesh - if (parser.seen('V')) { - #if ABL_PLANAR - planner.bed_level_matrix.debug(PSTR("Bed Level Correction Matrix:")); - #else - if (leveling_is_valid()) { - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - print_bilinear_leveling_grid(); - #if ENABLED(ABL_BILINEAR_SUBDIVISION) - print_bilinear_leveling_grid_virt(); - #endif - #elif ENABLED(MESH_BED_LEVELING) - SERIAL_ECHOLNPGM("Mesh Bed Level data:"); - mbl_mesh_report(); - #endif - } - #endif - } - - const bool to_enable = parser.boolval('S'); - if (parser.seen('S')) - set_bed_leveling_enabled(to_enable); - - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - if (parser.seen('Z')) set_z_fade_height(parser.value_linear_units()); - #endif - - const bool new_status = leveling_is_active(); - - if (to_enable && !new_status) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_M420_FAILED); - } - - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR("Bed Leveling ", new_status ? MSG_ON : MSG_OFF); - - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - SERIAL_ECHO_START(); - SERIAL_ECHOPGM("Fade Height "); - if (planner.z_fade_height > 0.0) - SERIAL_ECHOLN(planner.z_fade_height); - else - SERIAL_ECHOLNPGM(MSG_OFF); - #endif - } -#endif - -#if ENABLED(MESH_BED_LEVELING) - - /** - * M421: Set a single Mesh Bed Leveling Z coordinate - * - * Usage: - * M421 X Y Z - * M421 X Y Q - * M421 I J Z - * M421 I J Q - */ - inline void gcode_M421() { - const bool hasX = parser.seen('X'), hasI = parser.seen('I'); - const int8_t ix = hasI ? parser.value_int() : hasX ? mbl.probe_index_x(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_START(); - SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS); - } - else if (ix < 0 || iy < 0) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY); - } - else - mbl.set_z(ix, iy, parser.value_linear_units() + (hasQ ? mbl.z_values[ix][iy] : 0)); - } - -#elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - /** - * M421: Set a single Mesh Bed Leveling Z coordinate - * - * Usage: - * M421 I J Z - * M421 I J Q - */ - inline void gcode_M421() { - int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1); - const bool hasI = ix >= 0, - hasJ = iy >= 0, - hasZ = parser.seen('Z'), - hasQ = !hasZ && parser.seen('Q'); - - if (!hasI || !hasJ || !(hasZ || hasQ)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS); - } - else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY); - } - else { - z_values[ix][iy] = parser.value_linear_units() + (hasQ ? z_values[ix][iy] : 0); - #if ENABLED(ABL_BILINEAR_SUBDIVISION) - bed_level_virt_interpolate(); - #endif - } - } - -#elif ENABLED(AUTO_BED_LEVELING_UBL) - - /** - * M421: Set a single Mesh Bed Leveling Z coordinate - * - * Usage: - * M421 I J Z - * M421 I J Q - * M421 C Z - * M421 C Q - */ - inline void gcode_M421() { - int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1); - const bool hasI = ix >= 0, - hasJ = iy >= 0, - hasC = parser.seen('C'), - hasZ = parser.seen('Z'), - hasQ = !hasZ && parser.seen('Q'); - - if (hasC) { - const mesh_index_pair location = ubl.find_closest_mesh_point_of_type(REAL, current_position[X_AXIS], current_position[Y_AXIS], USE_NOZZLE_AS_REFERENCE, NULL, false); - ix = location.x_index; - iy = location.y_index; - } - - if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS); - } - else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY); - } - else - ubl.z_values[ix][iy] = parser.value_linear_units() + (hasQ ? ubl.z_values[ix][iy] : 0); - } - -#endif // AUTO_BED_LEVELING_UBL - -#if HAS_M206_COMMAND - - /** - * M428: Set home_offset based on the distance between the - * current_position and the nearest "reference point." - * If an axis is past center its endstop position - * is the reference-point. Otherwise it uses 0. This allows - * the Z offset to be set near the bed when using a max endstop. - * - * M428 can't be used more than 2cm away from 0 or an endstop. - * - * Use M206 to set these values directly. - */ - inline void gcode_M428() { - bool err = false; - LOOP_XYZ(i) { - if (axis_homed[i]) { - const float base = (current_position[i] > (soft_endstop_min[i] + soft_endstop_max[i]) * 0.5) ? base_home_pos((AxisEnum)i) : 0, - diff = base - RAW_POSITION(current_position[i], i); - if (WITHIN(diff, -20, 20)) { - set_home_offset((AxisEnum)i, diff); - } - else { - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_M428_TOO_FAR); - LCD_ALERTMESSAGEPGM("Err: Too far!"); - BUZZ(200, 40); - err = true; - break; - } - } - } - - if (!err) { - SYNC_PLAN_POSITION_KINEMATIC(); - report_current_position(); - LCD_MESSAGEPGM(MSG_HOME_OFFSETS_APPLIED); - BUZZ(100, 659); - BUZZ(100, 698); - } - } - -#endif // HAS_M206_COMMAND - -/** - * M500: Store settings in EEPROM - */ -inline void gcode_M500() { - (void)settings.save(); -} - -/** - * M501: Read settings from EEPROM - */ -inline void gcode_M501() { - (void)settings.load(); -} - -/** - * M502: Revert to default settings - */ -inline void gcode_M502() { - (void)settings.reset(); -} - -#if DISABLED(DISABLE_M503) - /** - * M503: print settings currently in memory - */ - inline void gcode_M503() { - (void)settings.report(!parser.boolval('S', true)); - } -#endif - -#if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) - - /** - * M540: Set whether SD card print should abort on endstop hit (M540 S<0|1>) - */ - inline void gcode_M540() { - if (parser.seen('S')) stepper.abort_on_endstop_hit = parser.value_bool(); - } - -#endif // ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED - -#if HAS_BED_PROBE - - void refresh_zprobe_zoffset(const bool no_babystep/*=false*/) { - static float last_zoffset = NAN; - - if (!isnan(last_zoffset)) { - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(BABYSTEP_ZPROBE_OFFSET) || ENABLED(DELTA) - const float diff = zprobe_zoffset - last_zoffset; - #endif - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - // Correct bilinear grid for new probe offset - if (diff) { - for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) - for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) - z_values[x][y] -= diff; - } - #if ENABLED(ABL_BILINEAR_SUBDIVISION) - bed_level_virt_interpolate(); - #endif - #endif - - #if ENABLED(BABYSTEP_ZPROBE_OFFSET) - if (!no_babystep && leveling_is_active()) - thermalManager.babystep_axis(Z_AXIS, -LROUND(diff * planner.axis_steps_per_mm[Z_AXIS])); - #else - UNUSED(no_babystep); - #endif - - #if ENABLED(DELTA) // correct the delta_height - home_offset[Z_AXIS] -= diff; - #endif - } - - last_zoffset = zprobe_zoffset; - } - - inline void gcode_M851() { - SERIAL_ECHO_START(); - SERIAL_ECHOPGM(MSG_ZPROBE_ZOFFSET " "); - if (parser.seen('Z')) { - const float value = parser.value_linear_units(); - if (WITHIN(value, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX)) { - zprobe_zoffset = value; - refresh_zprobe_zoffset(); - SERIAL_ECHO(zprobe_zoffset); - } - else - SERIAL_ECHOPGM(MSG_Z_MIN " " STRINGIFY(Z_PROBE_OFFSET_RANGE_MIN) " " MSG_Z_MAX " " STRINGIFY(Z_PROBE_OFFSET_RANGE_MAX)); - } - else - SERIAL_ECHOPAIR(": ", zprobe_zoffset); - - SERIAL_EOL(); - } - -#endif // HAS_BED_PROBE - -#if ENABLED(ADVANCED_PAUSE_FEATURE) - - /** - * M600: Pause for filament change - * - * E[distance] - Retract the filament this far (negative value) - * 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 (negative value) (manual reload) - * L[distance] - Extrude distance for insertion (positive value) (manual reload) - * B[count] - Number of times to beep, -1 for indefinite (if equipped with a buzzer) - * - * Default values are used for omitted arguments. - * - */ - inline void gcode_M600() { - - #if ENABLED(HOME_BEFORE_FILAMENT_CHANGE) - // Don't allow filament change without homing first - if (axis_unhomed_error()) home_all_axes(); - #endif - - // Initial retract before move to filament change position - const float retract = parser.seen('E') ? parser.value_axis_units(E_AXIS) : 0 - #ifdef PAUSE_PARK_RETRACT_LENGTH - - (PAUSE_PARK_RETRACT_LENGTH) - #endif - ; - - // Lift Z axis - const float z_lift = parser.linearval('Z', 0 - #ifdef PAUSE_PARK_Z_ADD - + PAUSE_PARK_Z_ADD - #endif - ); - - // Move XY axes to filament exchange position - const float x_pos = parser.linearval('X', 0 - #ifdef PAUSE_PARK_X_POS - + PAUSE_PARK_X_POS - #endif - ); - const float y_pos = parser.linearval('Y', 0 - #ifdef PAUSE_PARK_Y_POS - + PAUSE_PARK_Y_POS - #endif - ); - - // Unload filament - const float unload_length = parser.seen('U') ? parser.value_axis_units(E_AXIS) : 0 - #if defined(FILAMENT_CHANGE_UNLOAD_LENGTH) && FILAMENT_CHANGE_UNLOAD_LENGTH > 0 - - (FILAMENT_CHANGE_UNLOAD_LENGTH) - #endif - ; - - // Load filament - const float load_length = parser.seen('L') ? parser.value_axis_units(E_AXIS) : 0 - #ifdef FILAMENT_CHANGE_LOAD_LENGTH - + FILAMENT_CHANGE_LOAD_LENGTH - #endif - ; - - const int beep_count = parser.intval('B', - #ifdef FILAMENT_CHANGE_NUMBER_OF_ALERT_BEEPS - FILAMENT_CHANGE_NUMBER_OF_ALERT_BEEPS - #else - -1 - #endif - ); - - const bool job_running = print_job_timer.isRunning(); - - if (pause_print(retract, z_lift, x_pos, y_pos, unload_length, beep_count, true)) { - wait_for_filament_reload(beep_count); - resume_print(load_length, ADVANCED_PAUSE_EXTRUDE_LENGTH, beep_count); - } - - // Resume the print job timer if it was running - if (job_running) print_job_timer.start(); - } - -#endif // ADVANCED_PAUSE_FEATURE - -#if ENABLED(MK2_MULTIPLEXER) - - inline void select_multiplexed_stepper(const uint8_t e) { - stepper.synchronize(); - disable_e_steppers(); - WRITE(E_MUX0_PIN, TEST(e, 0) ? HIGH : LOW); - WRITE(E_MUX1_PIN, TEST(e, 1) ? HIGH : LOW); - WRITE(E_MUX2_PIN, TEST(e, 2) ? HIGH : LOW); - safe_delay(100); - } - - /** - * M702: Unload all extruders - */ - inline void gcode_M702() { - for (uint8_t s = 0; s < E_STEPPERS; s++) { - select_multiplexed_stepper(e); - // TODO: standard unload filament function - // MK2 firmware behavior: - // - Make sure temperature is high enough - // - Raise Z to at least 15 to make room - // - Extrude 1cm of filament in 1 second - // - Under 230C quickly purge ~12mm, over 230C purge ~10mm - // - Change E max feedrate to 80, eject the filament from the tube. Sync. - // - Restore E max feedrate to 50 - } - // Go back to the last active extruder - select_multiplexed_stepper(active_extruder); - disable_e_steppers(); - } - -#endif // MK2_MULTIPLEXER - -#if ENABLED(DUAL_X_CARRIAGE) - - /** - * M605: Set dual x-carriage movement mode - * - * M605 S0: Full control mode. The slicer has full control over x-carriage movement - * M605 S1: Auto-park mode. The inactive head will auto park/unpark without slicer involvement - * M605 S2 [Xnnn] [Rmmm]: Duplication mode. The second extruder will duplicate the first with nnn - * units x-offset and an optional differential hotend temperature of - * mmm degrees. E.g., with "M605 S2 X100 R2" the second extruder will duplicate - * the first with a spacing of 100mm in the x direction and 2 degrees hotter. - * - * Note: the X axis should be homed after changing dual x-carriage mode. - */ - inline void gcode_M605() { - stepper.synchronize(); - if (parser.seen('S')) dual_x_carriage_mode = (DualXMode)parser.value_byte(); - switch (dual_x_carriage_mode) { - case DXC_FULL_CONTROL_MODE: - case DXC_AUTO_PARK_MODE: - break; - case DXC_DUPLICATION_MODE: - if (parser.seen('X')) duplicate_extruder_x_offset = max(parser.value_linear_units(), X2_MIN_POS - x_home_pos(0)); - if (parser.seen('R')) duplicate_extruder_temp_offset = parser.value_celsius_diff(); - SERIAL_ECHO_START(); - SERIAL_ECHOPGM(MSG_HOTEND_OFFSET); - SERIAL_CHAR(' '); - SERIAL_ECHO(hotend_offset[X_AXIS][0]); - SERIAL_CHAR(','); - SERIAL_ECHO(hotend_offset[Y_AXIS][0]); - SERIAL_CHAR(' '); - SERIAL_ECHO(duplicate_extruder_x_offset); - SERIAL_CHAR(','); - SERIAL_ECHOLN(hotend_offset[Y_AXIS][1]); - break; - default: - dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; - break; - } - active_extruder_parked = false; - extruder_duplication_enabled = false; - delayed_move_time = 0; - } - -#elif ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) - - inline void gcode_M605() { - stepper.synchronize(); - extruder_duplication_enabled = parser.intval('S') == (int)DXC_DUPLICATION_MODE; - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(MSG_DUPLICATION_MODE, extruder_duplication_enabled ? MSG_ON : MSG_OFF); - } - -#endif // DUAL_NOZZLE_DUPLICATION_MODE - -#if ENABLED(LIN_ADVANCE) - /** - * M900: Set and/or Get advance K factor and WH/D ratio - * - * K Set advance K factor - * R Set ratio directly (overrides WH/D) - * W H D Set ratio from WH/D - */ - inline void gcode_M900() { - stepper.synchronize(); - - const float newK = parser.floatval('K', -1); - if (newK >= 0) planner.extruder_advance_k = newK; - - float newR = parser.floatval('R', -1); - if (newR < 0) { - const float newD = parser.floatval('D', -1), - newW = parser.floatval('W', -1), - newH = parser.floatval('H', -1); - if (newD >= 0 && newW >= 0 && newH >= 0) - newR = newD ? (newW * newH) / (sq(newD * 0.5) * M_PI) : 0; - } - if (newR >= 0) planner.advance_ed_ratio = newR; - - SERIAL_ECHO_START(); - SERIAL_ECHOPAIR("Advance K=", planner.extruder_advance_k); - SERIAL_ECHOPGM(" E/D="); - const float ratio = planner.advance_ed_ratio; - if (ratio) SERIAL_ECHO(ratio); else SERIAL_ECHOPGM("Auto"); - SERIAL_EOL(); - } -#endif // LIN_ADVANCE - -#if ENABLED(HAVE_TMC2130) - - static void tmc2130_get_current(TMC2130Stepper &st, const char name) { - SERIAL_CHAR(name); - SERIAL_ECHOPGM(" axis driver current: "); - SERIAL_ECHOLN(st.getCurrent()); - } - static void tmc2130_set_current(TMC2130Stepper &st, const char name, const int mA) { - st.setCurrent(mA, R_SENSE, HOLD_MULTIPLIER); - tmc2130_get_current(st, name); - } - - static void tmc2130_report_otpw(TMC2130Stepper &st, const char name) { - SERIAL_CHAR(name); - SERIAL_ECHOPGM(" axis temperature prewarn triggered: "); - serialprintPGM(st.getOTPW() ? PSTR("true") : PSTR("false")); - SERIAL_EOL(); - } - static void tmc2130_clear_otpw(TMC2130Stepper &st, const char name) { - st.clear_otpw(); - SERIAL_CHAR(name); - SERIAL_ECHOLNPGM(" prewarn flag cleared"); - } - - static void tmc2130_get_pwmthrs(TMC2130Stepper &st, const char name, const uint16_t spmm) { - SERIAL_CHAR(name); - SERIAL_ECHOPGM(" stealthChop max speed set to "); - SERIAL_ECHOLN(12650000UL * st.microsteps() / (256 * st.stealth_max_speed() * spmm)); - } - static void tmc2130_set_pwmthrs(TMC2130Stepper &st, const char name, const int32_t thrs, const uint32_t spmm) { - st.stealth_max_speed(12650000UL * st.microsteps() / (256 * thrs * spmm)); - tmc2130_get_pwmthrs(st, name, spmm); - } - - static void tmc2130_get_sgt(TMC2130Stepper &st, const char name) { - SERIAL_CHAR(name); - SERIAL_ECHOPGM(" driver homing sensitivity set to "); - SERIAL_ECHOLN(st.sgt()); - } - static void tmc2130_set_sgt(TMC2130Stepper &st, const char name, const int8_t sgt_val) { - st.sgt(sgt_val); - tmc2130_get_sgt(st, name); - } - - /** - * M906: Set motor current in milliamps using axis codes X, Y, Z, E - * Report driver currents when no axis specified - * - * S1: Enable automatic current control - * S0: Disable - */ - inline void gcode_M906() { - uint16_t values[XYZE]; - LOOP_XYZE(i) - values[i] = parser.intval(axis_codes[i]); - - #if ENABLED(X_IS_TMC2130) - if (values[X_AXIS]) tmc2130_set_current(stepperX, 'X', values[X_AXIS]); - else tmc2130_get_current(stepperX, 'X'); - #endif - #if ENABLED(Y_IS_TMC2130) - if (values[Y_AXIS]) tmc2130_set_current(stepperY, 'Y', values[Y_AXIS]); - else tmc2130_get_current(stepperY, 'Y'); - #endif - #if ENABLED(Z_IS_TMC2130) - if (values[Z_AXIS]) tmc2130_set_current(stepperZ, 'Z', values[Z_AXIS]); - else tmc2130_get_current(stepperZ, 'Z'); - #endif - #if ENABLED(E0_IS_TMC2130) - if (values[E_AXIS]) tmc2130_set_current(stepperE0, 'E', values[E_AXIS]); - else tmc2130_get_current(stepperE0, 'E'); - #endif - - #if ENABLED(AUTOMATIC_CURRENT_CONTROL) - if (parser.seen('S')) auto_current_control = parser.value_bool(); - #endif - } - - /** - * M911: Report TMC2130 stepper driver overtemperature pre-warn flag - * The flag is held by the library and persist until manually cleared by M912 - */ - inline void gcode_M911() { - const bool reportX = parser.seen('X'), reportY = parser.seen('Y'), reportZ = parser.seen('Z'), reportE = parser.seen('E'), - reportAll = (!reportX && !reportY && !reportZ && !reportE) || (reportX && reportY && reportZ && reportE); - #if ENABLED(X_IS_TMC2130) - if (reportX || reportAll) tmc2130_report_otpw(stepperX, 'X'); - #endif - #if ENABLED(Y_IS_TMC2130) - if (reportY || reportAll) tmc2130_report_otpw(stepperY, 'Y'); - #endif - #if ENABLED(Z_IS_TMC2130) - if (reportZ || reportAll) tmc2130_report_otpw(stepperZ, 'Z'); - #endif - #if ENABLED(E0_IS_TMC2130) - if (reportE || reportAll) tmc2130_report_otpw(stepperE0, 'E'); - #endif - } - - /** - * M912: Clear TMC2130 stepper driver overtemperature pre-warn flag held by the library - */ - inline void gcode_M912() { - const bool clearX = parser.seen('X'), clearY = parser.seen('Y'), clearZ = parser.seen('Z'), clearE = parser.seen('E'), - clearAll = (!clearX && !clearY && !clearZ && !clearE) || (clearX && clearY && clearZ && clearE); - #if ENABLED(X_IS_TMC2130) - if (clearX || clearAll) tmc2130_clear_otpw(stepperX, 'X'); - #endif - #if ENABLED(Y_IS_TMC2130) - if (clearY || clearAll) tmc2130_clear_otpw(stepperY, 'Y'); - #endif - #if ENABLED(Z_IS_TMC2130) - if (clearZ || clearAll) tmc2130_clear_otpw(stepperZ, 'Z'); - #endif - #if ENABLED(E0_IS_TMC2130) - if (clearE || clearAll) tmc2130_clear_otpw(stepperE0, 'E'); - #endif - } - - /** - * M913: Set HYBRID_THRESHOLD speed. - */ - #if ENABLED(HYBRID_THRESHOLD) - inline void gcode_M913() { - uint16_t values[XYZE]; - LOOP_XYZE(i) - values[i] = parser.intval(axis_codes[i]); - - #if ENABLED(X_IS_TMC2130) - if (values[X_AXIS]) tmc2130_set_pwmthrs(stepperX, 'X', values[X_AXIS], planner.axis_steps_per_mm[X_AXIS]); - else tmc2130_get_pwmthrs(stepperX, 'X', planner.axis_steps_per_mm[X_AXIS]); - #endif - #if ENABLED(Y_IS_TMC2130) - if (values[Y_AXIS]) tmc2130_set_pwmthrs(stepperY, 'Y', values[Y_AXIS], planner.axis_steps_per_mm[Y_AXIS]); - else tmc2130_get_pwmthrs(stepperY, 'Y', planner.axis_steps_per_mm[Y_AXIS]); - #endif - #if ENABLED(Z_IS_TMC2130) - if (values[Z_AXIS]) tmc2130_set_pwmthrs(stepperZ, 'Z', values[Z_AXIS], planner.axis_steps_per_mm[Z_AXIS]); - else tmc2130_get_pwmthrs(stepperZ, 'Z', planner.axis_steps_per_mm[Z_AXIS]); - #endif - #if ENABLED(E0_IS_TMC2130) - if (values[E_AXIS]) tmc2130_set_pwmthrs(stepperE0, 'E', values[E_AXIS], planner.axis_steps_per_mm[E_AXIS]); - else tmc2130_get_pwmthrs(stepperE0, 'E', planner.axis_steps_per_mm[E_AXIS]); - #endif - } - #endif // HYBRID_THRESHOLD - - /** - * M914: Set SENSORLESS_HOMING sensitivity. - */ - #if ENABLED(SENSORLESS_HOMING) - inline void gcode_M914() { - #if ENABLED(X_IS_TMC2130) - if (parser.seen(axis_codes[X_AXIS])) tmc2130_set_sgt(stepperX, 'X', parser.value_int()); - else tmc2130_get_sgt(stepperX, 'X'); - #endif - #if ENABLED(Y_IS_TMC2130) - if (parser.seen(axis_codes[Y_AXIS])) tmc2130_set_sgt(stepperY, 'Y', parser.value_int()); - else tmc2130_get_sgt(stepperY, 'Y'); - #endif - } - #endif // SENSORLESS_HOMING - -#endif // HAVE_TMC2130 - -/** - * M907: Set digital trimpot motor current using axis codes X, Y, Z, E, B, S - */ -inline void gcode_M907() { - #if HAS_DIGIPOTSS - - LOOP_XYZE(i) if (parser.seen(axis_codes[i])) stepper.digipot_current(i, parser.value_int()); - if (parser.seen('B')) stepper.digipot_current(4, parser.value_int()); - if (parser.seen('S')) for (uint8_t i = 0; i <= 4; i++) stepper.digipot_current(i, parser.value_int()); - - #elif HAS_MOTOR_CURRENT_PWM - - #if PIN_EXISTS(MOTOR_CURRENT_PWM_XY) - if (parser.seen('X')) stepper.digipot_current(0, parser.value_int()); - #endif - #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z) - if (parser.seen('Z')) stepper.digipot_current(1, parser.value_int()); - #endif - #if PIN_EXISTS(MOTOR_CURRENT_PWM_E) - if (parser.seen('E')) stepper.digipot_current(2, parser.value_int()); - #endif - - #endif - - #if ENABLED(DIGIPOT_I2C) - // this one uses actual amps in floating point - LOOP_XYZE(i) if (parser.seen(axis_codes[i])) digipot_i2c_set_current(i, parser.value_float()); - // for each additional extruder (named B,C,D,E..., channels 4,5,6,7...) - for (uint8_t i = NUM_AXIS; i < DIGIPOT_I2C_NUM_CHANNELS; i++) if (parser.seen('B' + i - (NUM_AXIS))) digipot_i2c_set_current(i, parser.value_float()); - #endif - - #if ENABLED(DAC_STEPPER_CURRENT) - if (parser.seen('S')) { - const float dac_percent = parser.value_float(); - for (uint8_t i = 0; i <= 4; i++) dac_current_percent(i, dac_percent); - } - LOOP_XYZE(i) if (parser.seen(axis_codes[i])) dac_current_percent(i, parser.value_float()); - #endif -} - -#if HAS_DIGIPOTSS || ENABLED(DAC_STEPPER_CURRENT) - - /** - * M908: Control digital trimpot directly (M908 P S) - */ - inline void gcode_M908() { - #if HAS_DIGIPOTSS - stepper.digitalPotWrite( - parser.intval('P'), - parser.intval('S') - ); - #endif - #ifdef DAC_STEPPER_CURRENT - dac_current_raw( - parser.byteval('P', -1), - parser.ushortval('S', 0) - ); - #endif - } - - #if ENABLED(DAC_STEPPER_CURRENT) // As with Printrbot RevF - - inline void gcode_M909() { dac_print_values(); } - - inline void gcode_M910() { dac_commit_eeprom(); } - - #endif - -#endif // HAS_DIGIPOTSS || DAC_STEPPER_CURRENT - -#if HAS_MICROSTEPS - - // M350 Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers. - inline void gcode_M350() { - if (parser.seen('S')) for (int i = 0; i <= 4; i++) stepper.microstep_mode(i, parser.value_byte()); - LOOP_XYZE(i) if (parser.seen(axis_codes[i])) stepper.microstep_mode(i, parser.value_byte()); - if (parser.seen('B')) stepper.microstep_mode(4, parser.value_byte()); - stepper.microstep_readings(); - } - - /** - * M351: Toggle MS1 MS2 pins directly with axis codes X Y Z E B - * S# determines MS1 or MS2, X# sets the pin high/low. - */ - inline void gcode_M351() { - if (parser.seenval('S')) switch (parser.value_byte()) { - case 1: - LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, parser.value_byte(), -1); - if (parser.seenval('B')) stepper.microstep_ms(4, parser.value_byte(), -1); - break; - case 2: - LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, -1, parser.value_byte()); - if (parser.seenval('B')) stepper.microstep_ms(4, -1, parser.value_byte()); - break; - } - stepper.microstep_readings(); - } - -#endif // HAS_MICROSTEPS - -#if HAS_CASE_LIGHT - #ifndef INVERT_CASE_LIGHT - #define INVERT_CASE_LIGHT false - #endif - int case_light_brightness; // LCD routine wants INT - bool case_light_on; - - void update_case_light() { - pinMode(CASE_LIGHT_PIN, OUTPUT); // digitalWrite doesn't set the port mode - uint8_t case_light_bright = (uint8_t)case_light_brightness; - if (case_light_on) { - if (USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) { - analogWrite(CASE_LIGHT_PIN, INVERT_CASE_LIGHT ? 255 - case_light_brightness : case_light_brightness ); - } - else WRITE(CASE_LIGHT_PIN, INVERT_CASE_LIGHT ? LOW : HIGH); - } - else WRITE(CASE_LIGHT_PIN, INVERT_CASE_LIGHT ? HIGH : LOW); - } -#endif // HAS_CASE_LIGHT - -/** - * M355: Turn case light on/off and set brightness - * - * P Set case light brightness (PWM pin required - ignored otherwise) - * - * S Set case light on/off - * - * When S turns on the light on a PWM pin then the current brightness level is used/restored - * - * M355 P200 S0 turns off the light & sets the brightness level - * M355 S1 turns on the light with a brightness of 200 (assuming a PWM pin) - */ -inline void gcode_M355() { - #if HAS_CASE_LIGHT - uint8_t args = 0; - if (parser.seenval('P')) ++args, case_light_brightness = parser.value_byte(); - if (parser.seenval('S')) ++args, case_light_on = parser.value_bool(); - if (args) update_case_light(); - - // always report case light status - SERIAL_ECHO_START(); - if (!case_light_on) { - SERIAL_ECHOLN("Case light: off"); - } - else { - if (!USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) SERIAL_ECHOLN("Case light: on"); - else SERIAL_ECHOLNPAIR("Case light: ", case_light_brightness); - } - - #else - SERIAL_ERROR_START(); - SERIAL_ERRORLNPGM(MSG_ERR_M355_NONE); - #endif // HAS_CASE_LIGHT -} - -#if ENABLED(MIXING_EXTRUDER) - - /** - * M163: Set a single mix factor for a mixing extruder - * This is called "weight" by some systems. - * - * S[index] The channel index to set - * P[float] The mix value - * - */ - inline void gcode_M163() { - const int mix_index = parser.intval('S'); - if (mix_index < MIXING_STEPPERS) { - float mix_value = parser.floatval('P'); - NOLESS(mix_value, 0.0); - mixing_factor[mix_index] = RECIPROCAL(mix_value); - } - } - - #if MIXING_VIRTUAL_TOOLS > 1 - - /** - * M164: Store the current mix factors as a virtual tool. - * - * S[index] The virtual tool to store - * - */ - inline void gcode_M164() { - const int tool_index = parser.intval('S'); - if (tool_index < MIXING_VIRTUAL_TOOLS) { - normalize_mix(); - for (uint8_t i = 0; i < MIXING_STEPPERS; i++) - mixing_virtual_tool_mix[tool_index][i] = mixing_factor[i]; - } - } - - #endif - - #if ENABLED(DIRECT_MIXING_IN_G1) - /** - * M165: Set multiple mix factors for a mixing extruder. - * Factors that are left out will be set to 0. - * All factors together must add up to 1.0. - * - * A[factor] Mix factor for extruder stepper 1 - * B[factor] Mix factor for extruder stepper 2 - * C[factor] Mix factor for extruder stepper 3 - * D[factor] Mix factor for extruder stepper 4 - * H[factor] Mix factor for extruder stepper 5 - * I[factor] Mix factor for extruder stepper 6 - * - */ - inline void gcode_M165() { gcode_get_mix(); } - #endif - -#endif // MIXING_EXTRUDER - -/** - * M999: Restart after being stopped - * - * Default behaviour is to flush the serial buffer and request - * a resend to the host starting on the last N line received. - * - * Sending "M999 S1" will resume printing without flushing the - * existing command buffer. - * - */ -inline void gcode_M999() { - Running = true; - lcd_reset_alert_level(); - - if (parser.boolval('S')) return; - - // gcode_LastN = Stopped_gcode_LastN; - FlushSerialRequestResend(); -} - -#if ENABLED(SWITCHING_EXTRUDER) - #if EXTRUDERS > 3 - #define REQ_ANGLES 4 - #define _SERVO_NR (e < 2 ? SWITCHING_EXTRUDER_SERVO_NR : SWITCHING_EXTRUDER_E23_SERVO_NR) - #else - #define REQ_ANGLES 2 - #define _SERVO_NR SWITCHING_EXTRUDER_SERVO_NR - #endif - inline void move_extruder_servo(const uint8_t e) { - constexpr int16_t angles[] = SWITCHING_EXTRUDER_SERVO_ANGLES; - static_assert(COUNT(angles) == REQ_ANGLES, "SWITCHING_EXTRUDER_SERVO_ANGLES needs " STRINGIFY(REQ_ANGLES) " angles."); - stepper.synchronize(); - #if EXTRUDERS & 1 - if (e < EXTRUDERS - 1) - #endif - { - MOVE_SERVO(_SERVO_NR, angles[e]); - safe_delay(500); - } - } -#endif // SWITCHING_EXTRUDER - -#if ENABLED(SWITCHING_NOZZLE) - inline void move_nozzle_servo(const uint8_t e) { - const int16_t angles[2] = SWITCHING_NOZZLE_SERVO_ANGLES; - stepper.synchronize(); - MOVE_SERVO(SWITCHING_NOZZLE_SERVO_NR, angles[e]); - safe_delay(500); - } -#endif - -inline void invalid_extruder_error(const uint8_t e) { - SERIAL_ECHO_START(); - SERIAL_CHAR('T'); - SERIAL_ECHO_F(e, DEC); - SERIAL_CHAR(' '); - SERIAL_ECHOLN(MSG_INVALID_EXTRUDER); -} - -#if ENABLED(PARKING_EXTRUDER) - - #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) - #define PE_MAGNET_ON_STATE !PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE - #else - #define PE_MAGNET_ON_STATE PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE - #endif - - void pe_set_magnet(const uint8_t extruder_num, const uint8_t state) { - switch (extruder_num) { - case 1: OUT_WRITE(SOL1_PIN, state); break; - default: OUT_WRITE(SOL0_PIN, state); break; - } - #if PARKING_EXTRUDER_SOLENOIDS_DELAY > 0 - dwell(PARKING_EXTRUDER_SOLENOIDS_DELAY); - #endif - } - - inline void pe_activate_magnet(const uint8_t extruder_num) { pe_set_magnet(extruder_num, PE_MAGNET_ON_STATE); } - inline void pe_deactivate_magnet(const uint8_t extruder_num) { pe_set_magnet(extruder_num, !PE_MAGNET_ON_STATE); } - -#endif // PARKING_EXTRUDER - -#if HAS_FANMUX - - void fanmux_switch(const uint8_t e) { - WRITE(FANMUX0_PIN, TEST(e, 0) ? HIGH : LOW); - #if PIN_EXISTS(FANMUX1) - WRITE(FANMUX1_PIN, TEST(e, 1) ? HIGH : LOW); - #if PIN_EXISTS(FANMUX2) - WRITE(FANMUX2, TEST(e, 2) ? HIGH : LOW); - #endif - #endif - } - - FORCE_INLINE void fanmux_init(void){ - SET_OUTPUT(FANMUX0_PIN); - #if PIN_EXISTS(FANMUX1) - SET_OUTPUT(FANMUX1_PIN); - #if PIN_EXISTS(FANMUX2) - SET_OUTPUT(FANMUX2_PIN); - #endif - #endif - fanmux_switch(0); - } - -#endif // HAS_FANMUX - -/** - * Perform a tool-change, which may result in moving the - * previous tool out of the way and the new tool into place. - */ -void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool no_move/*=false*/) { - #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1 - - if (tmp_extruder >= MIXING_VIRTUAL_TOOLS) - return invalid_extruder_error(tmp_extruder); - - // T0-Tnnn: Switch virtual tool by changing the mix - for (uint8_t j = 0; j < MIXING_STEPPERS; j++) - mixing_factor[j] = mixing_virtual_tool_mix[tmp_extruder][j]; - - #else // !MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1 - - if (tmp_extruder >= EXTRUDERS) - return invalid_extruder_error(tmp_extruder); - - #if HOTENDS > 1 - - const float old_feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : feedrate_mm_s; - - feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S; - - if (tmp_extruder != active_extruder) { - if (!no_move && axis_unhomed_error()) { - no_move = true; - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("No move on toolchange"); - #endif - } - - // Save current position to destination, for use later - set_destination_to_current(); - - #if ENABLED(DUAL_X_CARRIAGE) - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOPGM("Dual X Carriage Mode "); - switch (dual_x_carriage_mode) { - case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break; - case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break; - case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break; - } - } - #endif - - const float xhome = x_home_pos(active_extruder); - if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE - && IsRunning() - && (delayed_move_time || current_position[X_AXIS] != xhome) - ) { - float raised_z = current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT; - #if ENABLED(MAX_SOFTWARE_ENDSTOPS) - NOMORE(raised_z, soft_endstop_max[Z_AXIS]); - #endif - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPAIR("Raise to ", raised_z); - SERIAL_ECHOLNPAIR("MoveX to ", xhome); - SERIAL_ECHOLNPAIR("Lower to ", current_position[Z_AXIS]); - } - #endif - // Park old head: 1) raise 2) move to park position 3) lower - for (uint8_t i = 0; i < 3; i++) - planner.buffer_line( - i == 0 ? current_position[X_AXIS] : xhome, - current_position[Y_AXIS], - i == 2 ? current_position[Z_AXIS] : raised_z, - current_position[E_AXIS], - planner.max_feedrate_mm_s[i == 1 ? X_AXIS : Z_AXIS], - active_extruder - ); - stepper.synchronize(); - } - - // Apply Y & Z extruder offset (X offset is used as home pos with Dual X) - current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder]; - current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder]; - - // Activate the new extruder ahead of calling set_axis_is_at_home! - active_extruder = tmp_extruder; - - // This function resets the max/min values - the current position may be overwritten below. - set_axis_is_at_home(X_AXIS); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position); - #endif - - // Only when auto-parking are carriages safe to move - if (dual_x_carriage_mode != DXC_AUTO_PARK_MODE) no_move = true; - - switch (dual_x_carriage_mode) { - case DXC_FULL_CONTROL_MODE: - // New current position is the position of the activated extruder - current_position[X_AXIS] = LOGICAL_X_POSITION(inactive_extruder_x_pos); - // Save the inactive extruder's position (from the old current_position) - inactive_extruder_x_pos = RAW_X_POSITION(destination[X_AXIS]); - break; - case DXC_AUTO_PARK_MODE: - // record raised toolhead position for use by unpark - COPY(raised_parked_position, current_position); - raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT; - #if ENABLED(MAX_SOFTWARE_ENDSTOPS) - NOMORE(raised_parked_position[Z_AXIS], soft_endstop_max[Z_AXIS]); - #endif - active_extruder_parked = true; - delayed_move_time = 0; - break; - case DXC_DUPLICATION_MODE: - // If the new extruder is the left one, set it "parked" - // This triggers the second extruder to move into the duplication position - active_extruder_parked = (active_extruder == 0); - - if (active_extruder_parked) - current_position[X_AXIS] = LOGICAL_X_POSITION(inactive_extruder_x_pos); - else - current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset; - inactive_extruder_x_pos = RAW_X_POSITION(destination[X_AXIS]); - extruder_duplication_enabled = false; - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPAIR("Set inactive_extruder_x_pos=", inactive_extruder_x_pos); - SERIAL_ECHOLNPGM("Clear extruder_duplication_enabled"); - } - #endif - break; - } - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no"); - DEBUG_POS("New extruder (parked)", current_position); - } - #endif - - // No extra case for HAS_ABL in DUAL_X_CARRIAGE. Does that mean they don't work together? - - #else // !DUAL_X_CARRIAGE - - #if ENABLED(PARKING_EXTRUDER) // Dual Parking extruder - const float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder]; - float z_raise = 0; - if (!no_move) { - - const float parkingposx[] = PARKING_EXTRUDER_PARKING_X, - midpos = ((parkingposx[1] - parkingposx[0])/2) + parkingposx[0] + hotend_offset[X_AXIS][active_extruder], - grabpos = parkingposx[tmp_extruder] + hotend_offset[X_AXIS][active_extruder] - + (tmp_extruder == 0 ? -(PARKING_EXTRUDER_GRAB_DISTANCE) : PARKING_EXTRUDER_GRAB_DISTANCE); - /** - * Steps: - * 1. raise Z-Axis to have enough clearance - * 2. move to park poition of old extruder - * 3. disengage magnetc field, wait for delay - * 4. move near new extruder - * 5. engage magnetic field for new extruder - * 6. move to parking incl. offset of new extruder - * 7. lower Z-Axis - */ - - // STEP 1 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("Starting Autopark"); - if (DEBUGGING(LEVELING)) DEBUG_POS("current position:", current_position); - #endif - z_raise = PARKING_EXTRUDER_SECURITY_RAISE; - current_position[Z_AXIS] += z_raise; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(1) Raise Z-Axis "); - if (DEBUGGING(LEVELING)) DEBUG_POS("Moving to Raised Z-Position", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder); - stepper.synchronize(); - - // STEP 2 - current_position[X_AXIS] = parkingposx[active_extruder] + hotend_offset[X_AXIS][active_extruder]; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPAIR("(2) Park extruder ", active_extruder); - if (DEBUGGING(LEVELING)) DEBUG_POS("Moving ParkPos", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - stepper.synchronize(); - - // STEP 3 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(3) Disengage magnet "); - #endif - pe_deactivate_magnet(active_extruder); - - // STEP 4 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(4) Move to position near new extruder"); - #endif - current_position[X_AXIS] += (active_extruder == 0 ? 10 : -10); // move 10mm away from parked extruder - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("Moving away from parked extruder", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - stepper.synchronize(); - - // STEP 5 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(5) Engage magnetic field"); - #endif - - #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) - pe_activate_magnet(active_extruder); //just save power for inverted magnets - #endif - pe_activate_magnet(tmp_extruder); - - // STEP 6 - current_position[X_AXIS] = grabpos + (tmp_extruder == 0 ? (+10) : (-10)); - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - current_position[X_AXIS] = grabpos; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPAIR("(6) Unpark extruder ", tmp_extruder); - if (DEBUGGING(LEVELING)) DEBUG_POS("Move UnparkPos", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS]/2, active_extruder); - stepper.synchronize(); - - // Step 7 - current_position[X_AXIS] = midpos - hotend_offset[X_AXIS][tmp_extruder]; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(7) Move midway between hotends"); - if (DEBUGGING(LEVELING)) DEBUG_POS("Move midway to new extruder", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - stepper.synchronize(); - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("Autopark done."); - #endif - } - else { // nomove == true - // Only engage magnetic field for new extruder - pe_activate_magnet(tmp_extruder); - #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) - pe_activate_magnet(active_extruder); // Just save power for inverted magnets - #endif - } - current_position[Z_AXIS] -= hotend_offset[Z_AXIS][tmp_extruder] - hotend_offset[Z_AXIS][active_extruder]; // Apply Zoffset - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("Applying Z-offset", current_position); - #endif - - #endif // dualParking extruder - - #if ENABLED(SWITCHING_NOZZLE) - #define DONT_SWITCH (SWITCHING_EXTRUDER_SERVO_NR == SWITCHING_NOZZLE_SERVO_NR) - // <0 if the new nozzle is higher, >0 if lower. A bigger raise when lower. - const float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder], - z_raise = 0.3 + (z_diff > 0.0 ? z_diff : 0.0); - - // Always raise by some amount (destination copied from current_position earlier) - current_position[Z_AXIS] += z_raise; - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder); - move_nozzle_servo(tmp_extruder); - #endif - - /** - * Set current_position to the position of the new nozzle. - * Offsets are based on linear distance, so we need to get - * the resulting position in coordinate space. - * - * - With grid or 3-point leveling, offset XYZ by a tilted vector - * - With mesh leveling, update Z for the new position - * - Otherwise, just use the raw linear distance - * - * Software endstops are altered here too. Consider a case where: - * E0 at X=0 ... E1 at X=10 - * When we switch to E1 now X=10, but E1 can't move left. - * To express this we apply the change in XY to the software endstops. - * E1 can move farther right than E0, so the right limit is extended. - * - * Note that we don't adjust the Z software endstops. Why not? - * Consider a case where Z=0 (here) and switching to E1 makes Z=1 - * because the bed is 1mm lower at the new position. As long as - * the first nozzle is out of the way, the carriage should be - * allowed to move 1mm lower. This technically "breaks" the - * Z software endstop. But this is technically correct (and - * there is no viable alternative). - */ - #if ABL_PLANAR - // Offset extruder, make sure to apply the bed level rotation matrix - vector_3 tmp_offset_vec = vector_3(hotend_offset[X_AXIS][tmp_extruder], - hotend_offset[Y_AXIS][tmp_extruder], - 0), - act_offset_vec = vector_3(hotend_offset[X_AXIS][active_extruder], - hotend_offset[Y_AXIS][active_extruder], - 0), - offset_vec = tmp_offset_vec - act_offset_vec; - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - tmp_offset_vec.debug(PSTR("tmp_offset_vec")); - act_offset_vec.debug(PSTR("act_offset_vec")); - offset_vec.debug(PSTR("offset_vec (BEFORE)")); - } - #endif - - offset_vec.apply_rotation(planner.bed_level_matrix.transpose(planner.bed_level_matrix)); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) offset_vec.debug(PSTR("offset_vec (AFTER)")); - #endif - - // Adjustments to the current position - const float xydiff[2] = { offset_vec.x, offset_vec.y }; - current_position[Z_AXIS] += offset_vec.z; - - #else // !ABL_PLANAR - - const float xydiff[2] = { - hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder], - hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder] - }; - - #if ENABLED(MESH_BED_LEVELING) - - if (leveling_is_active()) { - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) SERIAL_ECHOPAIR("Z before MBL: ", current_position[Z_AXIS]); - #endif - float x2 = current_position[X_AXIS] + xydiff[X_AXIS], - y2 = current_position[Y_AXIS] + xydiff[Y_AXIS], - z1 = current_position[Z_AXIS], z2 = z1; - planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], z1); - planner.apply_leveling(x2, y2, z2); - current_position[Z_AXIS] += z2 - z1; - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) - SERIAL_ECHOLNPAIR(" after: ", current_position[Z_AXIS]); - #endif - } - - #endif // MESH_BED_LEVELING - - #endif // !HAS_ABL - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOPAIR("Offset Tool XY by { ", xydiff[X_AXIS]); - SERIAL_ECHOPAIR(", ", xydiff[Y_AXIS]); - SERIAL_ECHOLNPGM(" }"); - } - #endif - - // The newly-selected extruder XY is actually at... - current_position[X_AXIS] += xydiff[X_AXIS]; - current_position[Y_AXIS] += xydiff[Y_AXIS]; - #if HAS_WORKSPACE_OFFSET || ENABLED(DUAL_X_CARRIAGE) || ENABLED(PARKING_EXTRUDER) - for (uint8_t i = X_AXIS; i <= Y_AXIS; i++) { - #if HAS_POSITION_SHIFT - position_shift[i] += xydiff[i]; - #endif - update_software_endstops((AxisEnum)i); - } - #endif - - // Set the new active extruder - active_extruder = tmp_extruder; - - #endif // !DUAL_X_CARRIAGE - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("Sync After Toolchange", current_position); - #endif - - // Tell the planner the new "current position" - SYNC_PLAN_POSITION_KINEMATIC(); - - // Move to the "old position" (move the extruder into place) - if (!no_move && IsRunning()) { - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination); - #endif - prepare_move_to_destination(); - } - - #if ENABLED(SWITCHING_NOZZLE) - // Move back down, if needed. (Including when the new tool is higher.) - if (z_raise != z_diff) { - destination[Z_AXIS] += z_diff; - feedrate_mm_s = planner.max_feedrate_mm_s[Z_AXIS]; - prepare_move_to_destination(); - } - #endif - - } // (tmp_extruder != active_extruder) - - stepper.synchronize(); - - #if ENABLED(EXT_SOLENOID) && !ENABLED(PARKING_EXTRUDER) - disable_all_solenoids(); - enable_solenoid_on_active_extruder(); - #endif // EXT_SOLENOID - - feedrate_mm_s = old_feedrate_mm_s; - - #else // HOTENDS <= 1 - - UNUSED(fr_mm_s); - UNUSED(no_move); - - #if ENABLED(MK2_MULTIPLEXER) - if (tmp_extruder >= E_STEPPERS) - return invalid_extruder_error(tmp_extruder); - - select_multiplexed_stepper(tmp_extruder); - #endif - - // Set the new active extruder - active_extruder = tmp_extruder; - - #endif // HOTENDS <= 1 - - #if ENABLED(SWITCHING_EXTRUDER) && !DONT_SWITCH - stepper.synchronize(); - move_extruder_servo(active_extruder); - #endif - - #if HAS_FANMUX - fanmux_switch(active_extruder); - #endif - - SERIAL_ECHO_START(); - SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, (int)active_extruder); - - #endif // !MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1 -} - -/** - * T0-T3: Switch tool, usually switching extruders - * - * F[units/min] Set the movement feedrate - * S1 Don't move the tool in XY after change - */ -inline void gcode_T(uint8_t tmp_extruder) { - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOPAIR(">>> gcode_T(", tmp_extruder); - SERIAL_CHAR(')'); - SERIAL_EOL(); - DEBUG_POS("BEFORE", current_position); - } - #endif - - #if HOTENDS == 1 || (ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1) - - tool_change(tmp_extruder); - - #elif HOTENDS > 1 - - tool_change( - tmp_extruder, - MMM_TO_MMS(parser.linearval('F')), - (tmp_extruder == active_extruder) || parser.boolval('S') - ); - - #endif - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - DEBUG_POS("AFTER", current_position); - SERIAL_ECHOLNPGM("<<< gcode_T"); - } - #endif -} +#if ENABLED(M100_FREE_MEMORY_WATCHER) + #include "gcode/calibrate/M100.h" +#endif /** - * Process a single command and dispatch it to its handler - * This is called from the main loop() + * Output the current position to serial */ -void process_next_command() { - char * const current_command = command_queue[cmd_queue_index_r]; - - if (DEBUGGING(ECHO)) { - SERIAL_ECHO_START(); - SERIAL_ECHOLN(current_command); - #if ENABLED(M100_FREE_MEMORY_WATCHER) - SERIAL_ECHOPAIR("slot:", cmd_queue_index_r); - M100_dump_routine(" Command Queue:", (const char*)command_queue, (const char*)(command_queue + sizeof(command_queue))); - #endif - } - - KEEPALIVE_STATE(IN_HANDLER); - - // Parse the next command in the queue - parser.parse(current_command); - - // Handle a known G, M, or T - switch (parser.command_letter) { - case 'G': switch (parser.codenum) { - - // G0, G1 - case 0: - case 1: - #if IS_SCARA - gcode_G0_G1(parser.codenum == 0); - #else - gcode_G0_G1(); - #endif - break; - - // G2, G3 - #if ENABLED(ARC_SUPPORT) && DISABLED(SCARA) - case 2: // G2: CW ARC - case 3: // G3: CCW ARC - gcode_G2_G3(parser.codenum == 2); - break; - #endif - - // G4 Dwell - case 4: - gcode_G4(); - break; - - #if ENABLED(BEZIER_CURVE_SUPPORT) - case 5: // G5: Cubic B_spline - gcode_G5(); - break; - #endif // BEZIER_CURVE_SUPPORT - - #if ENABLED(FWRETRACT) - case 10: // G10: retract - gcode_G10(); - break; - case 11: // G11: retract_recover - gcode_G11(); - break; - #endif // FWRETRACT - - #if ENABLED(NOZZLE_CLEAN_FEATURE) - case 12: - gcode_G12(); // G12: Nozzle Clean - break; - #endif // NOZZLE_CLEAN_FEATURE - - #if ENABLED(CNC_WORKSPACE_PLANES) - case 17: // G17: Select Plane XY - gcode_G17(); - break; - case 18: // G18: Select Plane ZX - gcode_G18(); - break; - case 19: // G19: Select Plane YZ - gcode_G19(); - break; - #endif // CNC_WORKSPACE_PLANES - - #if ENABLED(INCH_MODE_SUPPORT) - case 20: // G20: Inch Mode - gcode_G20(); - break; - - case 21: // G21: MM Mode - gcode_G21(); - break; - #endif // INCH_MODE_SUPPORT - - #if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(UBL_G26_MESH_VALIDATION) - case 26: // G26: Mesh Validation Pattern generation - gcode_G26(); - break; - #endif // AUTO_BED_LEVELING_UBL - - #if ENABLED(NOZZLE_PARK_FEATURE) - case 27: // G27: Nozzle Park - gcode_G27(); - break; - #endif // NOZZLE_PARK_FEATURE - - case 28: // G28: Home all axes, one at a time - gcode_G28(false); - break; - - #if HAS_LEVELING - case 29: // G29 Detailed Z probe, probes the bed at 3 or more points, - // or provides access to the UBL System if enabled. - gcode_G29(); - break; - #endif // HAS_LEVELING - - #if HAS_BED_PROBE - - case 30: // G30 Single Z probe - gcode_G30(); - break; - - #if ENABLED(Z_PROBE_SLED) - - case 31: // G31: dock the sled - gcode_G31(); - break; - - case 32: // G32: undock the sled - gcode_G32(); - break; - - #endif // Z_PROBE_SLED - - #endif // HAS_BED_PROBE - - #if PROBE_SELECTED - - #if ENABLED(DELTA_AUTO_CALIBRATION) - - case 33: // G33: Delta Auto-Calibration - gcode_G33(); - break; - - #endif // DELTA_AUTO_CALIBRATION - - #endif // PROBE_SELECTED - - #if ENABLED(G38_PROBE_TARGET) - case 38: // G38.2 & G38.3 - if (parser.subcode == 2 || parser.subcode == 3) - gcode_G38(parser.subcode == 2); - break; - #endif - - case 90: // G90 - relative_mode = false; - break; - case 91: // G91 - relative_mode = true; - break; - - case 92: // G92 - gcode_G92(); - break; - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(MESH_BED_LEVELING) - case 42: - gcode_G42(); - break; - #endif - - #if ENABLED(DEBUG_GCODE_PARSER) - case 800: - parser.debug(); // GCode Parser Test for G - break; - #endif - } - break; - - case 'M': switch (parser.codenum) { - #if HAS_RESUME_CONTINUE - case 0: // M0: Unconditional stop - Wait for user button press on LCD - case 1: // M1: Conditional stop - Wait for user button press on LCD - gcode_M0_M1(); - break; - #endif // ULTIPANEL - - #if ENABLED(SPINDLE_LASER_ENABLE) - case 3: - gcode_M3_M4(true); // M3: turn spindle/laser on, set laser/spindle power/speed, set rotation direction CW - break; // synchronizes with movement commands - case 4: - gcode_M3_M4(false); // M4: turn spindle/laser on, set laser/spindle power/speed, set rotation direction CCW - break; // synchronizes with movement commands - case 5: - gcode_M5(); // M5 - turn spindle/laser off - break; // synchronizes with movement commands - #endif - case 17: // M17: Enable all stepper motors - gcode_M17(); - break; - - #if ENABLED(SDSUPPORT) - case 20: // M20: list SD card - gcode_M20(); break; - case 21: // M21: init SD card - gcode_M21(); break; - case 22: // M22: release SD card - gcode_M22(); break; - case 23: // M23: Select file - gcode_M23(); break; - case 24: // M24: Start SD print - gcode_M24(); break; - case 25: // M25: Pause SD print - gcode_M25(); break; - case 26: // M26: Set SD index - gcode_M26(); break; - case 27: // M27: Get SD status - gcode_M27(); break; - case 28: // M28: Start SD write - gcode_M28(); break; - case 29: // M29: Stop SD write - gcode_M29(); break; - case 30: // M30 Delete File - gcode_M30(); break; - case 32: // M32: Select file and start SD print - gcode_M32(); break; - - #if ENABLED(LONG_FILENAME_HOST_SUPPORT) - case 33: // M33: Get the long full path to a file or folder - gcode_M33(); break; - #endif - - #if ENABLED(SDCARD_SORT_ALPHA) && ENABLED(SDSORT_GCODE) - case 34: // M34: Set SD card sorting options - gcode_M34(); break; - #endif // SDCARD_SORT_ALPHA && SDSORT_GCODE - - case 928: // M928: Start SD write - gcode_M928(); break; - #endif // SDSUPPORT - - case 31: // M31: Report time since the start of SD print or last M109 - gcode_M31(); break; - - case 42: // M42: Change pin state - gcode_M42(); break; - - #if ENABLED(PINS_DEBUGGING) - case 43: // M43: Read pin state - gcode_M43(); break; - #endif - - - #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) - case 48: // M48: Z probe repeatability test - gcode_M48(); - break; - #endif // Z_MIN_PROBE_REPEATABILITY_TEST - - #if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(UBL_G26_MESH_VALIDATION) - case 49: // M49: Turn on or off G26 debug flag for verbose output - gcode_M49(); - break; - #endif // AUTO_BED_LEVELING_UBL && UBL_G26_MESH_VALIDATION - - case 75: // M75: Start print timer - gcode_M75(); break; - case 76: // M76: Pause print timer - gcode_M76(); break; - case 77: // M77: Stop print timer - gcode_M77(); break; - - #if ENABLED(PRINTCOUNTER) - case 78: // M78: Show print statistics - gcode_M78(); break; - #endif - - #if ENABLED(M100_FREE_MEMORY_WATCHER) - case 100: // M100: Free Memory Report - gcode_M100(); - break; - #endif - - case 104: // M104: Set hot end temperature - gcode_M104(); - break; - - case 110: // M110: Set Current Line Number - gcode_M110(); - break; - - case 111: // M111: Set debug level - gcode_M111(); - break; - - #if DISABLED(EMERGENCY_PARSER) - - case 108: // M108: Cancel Waiting - gcode_M108(); - break; - - case 112: // M112: Emergency Stop - gcode_M112(); - break; - - case 410: // M410 quickstop - Abort all the planned moves. - gcode_M410(); - break; - - #endif - - #if ENABLED(HOST_KEEPALIVE_FEATURE) - case 113: // M113: Set Host Keepalive interval - gcode_M113(); - break; - #endif - - case 140: // M140: Set bed temperature - gcode_M140(); - break; - - - case 105: // M105: Report current temperature - gcode_M105(); - KEEPALIVE_STATE(NOT_BUSY); - return; // "ok" already printed - - #if ENABLED(AUTO_REPORT_TEMPERATURES) && (HAS_TEMP_HOTEND || HAS_TEMP_BED) - case 155: // M155: Set temperature auto-report interval - gcode_M155(); - break; - #endif - - case 109: // M109: Wait for hotend temperature to reach target - gcode_M109(); - break; - - #if HAS_TEMP_BED - case 190: // M190: Wait for bed temperature to reach target - gcode_M190(); - break; - #endif // HAS_TEMP_BED - - #if FAN_COUNT > 0 - case 106: // M106: Fan On - gcode_M106(); - break; - case 107: // M107: Fan Off - gcode_M107(); - break; - #endif // FAN_COUNT > 0 - - #if ENABLED(PARK_HEAD_ON_PAUSE) - case 125: // M125: Store current position and move to filament change position - gcode_M125(); break; - #endif - - #if ENABLED(BARICUDA) - // PWM for HEATER_1_PIN - #if HAS_HEATER_1 - case 126: // M126: valve open - gcode_M126(); - break; - case 127: // M127: valve closed - gcode_M127(); - break; - #endif // HAS_HEATER_1 - - // PWM for HEATER_2_PIN - #if HAS_HEATER_2 - case 128: // M128: valve open - gcode_M128(); - break; - case 129: // M129: valve closed - gcode_M129(); - break; - #endif // HAS_HEATER_2 - #endif // BARICUDA - - #if HAS_POWER_SWITCH - - case 80: // M80: Turn on Power Supply - gcode_M80(); - break; - - #endif // HAS_POWER_SWITCH - - case 81: // M81: Turn off Power, including Power Supply, if possible - gcode_M81(); - break; - - case 82: // M82: Set E axis normal mode (same as other axes) - gcode_M82(); - break; - case 83: // M83: Set E axis relative mode - gcode_M83(); - break; - case 18: // M18 => M84 - case 84: // M84: Disable all steppers or set timeout - gcode_M18_M84(); - break; - case 85: // M85: Set inactivity stepper shutdown timeout - gcode_M85(); - break; - case 92: // M92: Set the steps-per-unit for one or more axes - gcode_M92(); - break; - case 114: // M114: Report current position - gcode_M114(); - break; - case 115: // M115: Report capabilities - gcode_M115(); - break; - case 117: // M117: Set LCD message text, if possible - gcode_M117(); - break; - case 118: // M118: Display a message in the host console - gcode_M118(); - break; - case 119: // M119: Report endstop states - gcode_M119(); - break; - case 120: // M120: Enable endstops - gcode_M120(); - break; - case 121: // M121: Disable endstops - gcode_M121(); - break; - - #if ENABLED(ULTIPANEL) - - case 145: // M145: Set material heatup parameters - gcode_M145(); - break; - - #endif - - #if ENABLED(TEMPERATURE_UNITS_SUPPORT) - case 149: // M149: Set temperature units - gcode_M149(); - break; - #endif - - #if HAS_COLOR_LEDS - - case 150: // M150: Set Status LED Color - gcode_M150(); - break; - - #endif // HAS_COLOR_LEDS - - #if ENABLED(MIXING_EXTRUDER) - case 163: // M163: Set a component weight for mixing extruder - gcode_M163(); - break; - #if MIXING_VIRTUAL_TOOLS > 1 - case 164: // M164: Save current mix as a virtual extruder - gcode_M164(); - break; - #endif - #if ENABLED(DIRECT_MIXING_IN_G1) - case 165: // M165: Set multiple mix weights - gcode_M165(); - break; - #endif - #endif - - case 200: // M200: Set filament diameter, E to cubic units - gcode_M200(); - break; - case 201: // M201: Set max acceleration for print moves (units/s^2) - gcode_M201(); - break; - #if 0 // Not used for Sprinter/grbl gen6 - case 202: // M202 - gcode_M202(); - break; - #endif - case 203: // M203: Set max feedrate (units/sec) - gcode_M203(); - break; - case 204: // M204: Set acceleration - gcode_M204(); - break; - case 205: // M205: Set advanced settings - gcode_M205(); - break; - - #if HAS_M206_COMMAND - case 206: // M206: Set home offsets - gcode_M206(); - break; - #endif - - #if ENABLED(DELTA) - case 665: // M665: Set delta configurations - gcode_M665(); - break; - #endif - - #if ENABLED(DELTA) || ENABLED(Z_DUAL_ENDSTOPS) - case 666: // M666: Set delta or dual endstop adjustment - gcode_M666(); - break; - #endif - - #if ENABLED(FWRETRACT) - case 207: // M207: Set Retract Length, Feedrate, and Z lift - gcode_M207(); - break; - case 208: // M208: Set Recover (unretract) Additional Length and Feedrate - gcode_M208(); - break; - case 209: // M209: Turn Automatic Retract Detection on/off - if (MIN_AUTORETRACT <= MAX_AUTORETRACT) gcode_M209(); - break; - #endif // FWRETRACT - - case 211: // M211: Enable, Disable, and/or Report software endstops - gcode_M211(); - break; - - #if HOTENDS > 1 - case 218: // M218: Set a tool offset - gcode_M218(); - break; - #endif - - case 220: // M220: Set Feedrate Percentage: S ("FR" on your LCD) - gcode_M220(); - break; - - case 221: // M221: Set Flow Percentage - gcode_M221(); - break; - - case 226: // M226: Wait until a pin reaches a state - gcode_M226(); - break; - - #if HAS_SERVOS - case 280: // M280: Set servo position absolute - gcode_M280(); - break; - #endif // HAS_SERVOS - - #if HAS_BUZZER - case 300: // M300: Play beep tone - gcode_M300(); - break; - #endif // HAS_BUZZER - - #if ENABLED(PIDTEMP) - case 301: // M301: Set hotend PID parameters - gcode_M301(); - break; - #endif // PIDTEMP - - #if ENABLED(PIDTEMPBED) - case 304: // M304: Set bed PID parameters - gcode_M304(); - break; - #endif // PIDTEMPBED - - #if defined(CHDK) || HAS_PHOTOGRAPH - case 240: // M240: Trigger a camera by emulating a Canon RC-1 : http://www.doc-diy.net/photo/rc-1_hacked/ - gcode_M240(); - break; - #endif // CHDK || PHOTOGRAPH_PIN - - #if HAS_LCD_CONTRAST - case 250: // M250: Set LCD contrast - gcode_M250(); - break; - #endif // HAS_LCD_CONTRAST - - #if ENABLED(EXPERIMENTAL_I2CBUS) - - case 260: // M260: Send data to an i2c slave - gcode_M260(); - break; - - case 261: // M261: Request data from an i2c slave - gcode_M261(); - break; - - #endif // EXPERIMENTAL_I2CBUS +void report_current_position() { + SERIAL_PROTOCOLPGM("X:"); + SERIAL_PROTOCOL(current_position[X_AXIS]); + SERIAL_PROTOCOLPGM(" Y:"); + SERIAL_PROTOCOL(current_position[Y_AXIS]); + SERIAL_PROTOCOLPGM(" Z:"); + SERIAL_PROTOCOL(current_position[Z_AXIS]); + SERIAL_PROTOCOLPGM(" E:"); + SERIAL_PROTOCOL(current_position[E_AXIS]); - #if ENABLED(PREVENT_COLD_EXTRUSION) - case 302: // M302: Allow cold extrudes (set the minimum extrude temperature) - gcode_M302(); - break; - #endif // PREVENT_COLD_EXTRUSION + stepper.report_positions(); - case 303: // M303: PID autotune - gcode_M303(); - break; + #if IS_SCARA + SERIAL_PROTOCOLPAIR("SCARA Theta:", stepper.get_axis_position_degrees(A_AXIS)); + SERIAL_PROTOCOLLNPAIR(" Psi+Theta:", stepper.get_axis_position_degrees(B_AXIS)); + SERIAL_EOL(); + #endif +} - #if ENABLED(MORGAN_SCARA) - case 360: // M360: SCARA Theta pos1 - if (gcode_M360()) return; - break; - case 361: // M361: SCARA Theta pos2 - if (gcode_M361()) return; - break; - case 362: // M362: SCARA Psi pos1 - if (gcode_M362()) return; - break; - case 363: // M363: SCARA Psi pos2 - if (gcode_M363()) return; - break; - case 364: // M364: SCARA Psi pos3 (90 deg to Theta) - if (gcode_M364()) return; - break; - #endif // SCARA +#include "gcode/host/M114.h" +#include "gcode/host/M115.h" - case 400: // M400: Finish all moves - gcode_M400(); - break; +#include "gcode/lcd/M117.h" - #if HAS_BED_PROBE - case 401: // M401: Deploy probe - gcode_M401(); - break; - case 402: // M402: Stow probe - gcode_M402(); - break; - #endif // HAS_BED_PROBE +#include "gcode/host/M118.h" +#include "gcode/host/M119.h" - #if ENABLED(FILAMENT_WIDTH_SENSOR) - case 404: // M404: Enter the nominal filament width (3mm, 1.75mm ) N<3.0> or display nominal filament width - gcode_M404(); - break; - case 405: // M405: Turn on filament sensor for control - gcode_M405(); - break; - case 406: // M406: Turn off filament sensor for control - gcode_M406(); - break; - case 407: // M407: Display measured filament diameter - gcode_M407(); - break; - #endif // FILAMENT_WIDTH_SENSOR +#include "gcode/control/M120_M121.h" - #if HAS_LEVELING - case 420: // M420: Enable/Disable Bed Leveling - gcode_M420(); - break; - #endif +#if ENABLED(PARK_HEAD_ON_PAUSE) + #include "gcode/feature/pause/M125.h" +#endif - #if ENABLED(MESH_BED_LEVELING) || ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(AUTO_BED_LEVELING_BILINEAR) - case 421: // M421: Set a Mesh Bed Leveling Z coordinate - gcode_M421(); - break; - #endif +#if HAS_COLOR_LEDS + #include "gcode/feature/leds/M150.h" +#endif - #if HAS_M206_COMMAND - case 428: // M428: Apply current_position to home_offset - gcode_M428(); - break; - #endif +#include "gcode/config/M200.h" +#include "gcode/config/M201.h" - case 500: // M500: Store settings in EEPROM - gcode_M500(); - break; - case 501: // M501: Read settings from EEPROM - gcode_M501(); - break; - case 502: // M502: Revert to default settings - gcode_M502(); - break; - - #if DISABLED(DISABLE_M503) - case 503: // M503: print settings currently in memory - gcode_M503(); - break; - #endif +#if 0 // Not used for Sprinter/grbl gen6 + #include "gcode/config/M202.h" +#endif - #if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) - case 540: // M540: Set abort on endstop hit for SD printing - gcode_M540(); - break; - #endif +#include "gcode/config/M203.h" +#include "gcode/config/M204.h" +#include "gcode/config/M205.h" - #if HAS_BED_PROBE - case 851: // M851: Set Z Probe Z Offset - gcode_M851(); - break; - #endif // HAS_BED_PROBE +#if HAS_M206_COMMAND + #include "gcode/geometry/M206.h" +#endif - #if ENABLED(ADVANCED_PAUSE_FEATURE) - case 600: // M600: Pause for filament change - gcode_M600(); - break; - #endif // ADVANCED_PAUSE_FEATURE +#if IS_KINEMATIC + #include "gcode/calibrate/M665.h" +#endif - #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) - case 605: // M605: Set Dual X Carriage movement mode - gcode_M605(); - break; - #endif // DUAL_X_CARRIAGE +#if ENABLED(DELTA) || ENABLED(Z_DUAL_ENDSTOPS) + #include "gcode/calibrate/M666.h" +#endif - #if ENABLED(MK2_MULTIPLEXER) - case 702: // M702: Unload all extruders - gcode_M702(); - break; - #endif +#if ENABLED(FWRETRACT) + #include "gcode/feature/fwretract/M207.h" + #include "gcode/feature/fwretract/M208.h" + #include "gcode/feature/fwretract/M209.h" +#endif - #if ENABLED(LIN_ADVANCE) - case 900: // M900: Set advance K factor. - gcode_M900(); - break; - #endif +#include "gcode/control/M211.h" - #if ENABLED(HAVE_TMC2130) - case 906: // M906: Set motor current in milliamps using axis codes X, Y, Z, E - gcode_M906(); - break; - #endif +#if HOTENDS > 1 + #include "gcode/config/M218.h" +#endif - case 907: // M907: Set digital trimpot motor current using axis codes. - gcode_M907(); - break; +#include "gcode/config/M220.h" +#include "gcode/config/M221.h" - #if HAS_DIGIPOTSS || ENABLED(DAC_STEPPER_CURRENT) +#include "gcode/control/M226.h" - case 908: // M908: Control digital trimpot directly. - gcode_M908(); - break; +#if ENABLED(EXPERIMENTAL_I2CBUS) + #include "gcode/feature/i2c/M260_M261.h" +#endif - #if ENABLED(DAC_STEPPER_CURRENT) // As with Printrbot RevF +#if HAS_SERVOS + #include "gcode/control/M280.h" +#endif - case 909: // M909: Print digipot/DAC current value - gcode_M909(); - break; +#if HAS_BUZZER + #include "gcode/lcd/M300.h" +#endif - case 910: // M910: Commit digipot/DAC value to external EEPROM - gcode_M910(); - break; +#if ENABLED(PIDTEMP) + #include "gcode/config/M301.h" +#endif - #endif +#if ENABLED(PIDTEMPBED) + #include "gcode/config/M304.h" +#endif - #endif // HAS_DIGIPOTSS || DAC_STEPPER_CURRENT +#if defined(CHDK) || HAS_PHOTOGRAPH + #include "gcode/control/M240.h" +#endif - #if ENABLED(HAVE_TMC2130) - case 911: // M911: Report TMC2130 prewarn triggered flags - gcode_M911(); - break; +#if HAS_LCD_CONTRAST + #include "gcode/control/M250.h" +#endif - case 912: // M911: Clear TMC2130 prewarn triggered flags - gcode_M912(); - break; +#if ENABLED(PREVENT_COLD_EXTRUSION) + #include "gcode/config/M302.h" +#endif - #if ENABLED(HYBRID_THRESHOLD) - case 913: // M913: Set HYBRID_THRESHOLD speed. - gcode_M913(); - break; - #endif +#include "gcode/temperature/M303.h" - #if ENABLED(SENSORLESS_HOMING) - case 914: // M914: Set SENSORLESS_HOMING sensitivity. - gcode_M914(); - break; - #endif - #endif +#if ENABLED(MORGAN_SCARA) + #include "gcode/scara/M360-M364.h" +#endif - #if HAS_MICROSTEPS +#if ENABLED(EXT_SOLENOID) + #include "gcode/control/M380_M381.h" +#endif - case 350: // M350: Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers. - gcode_M350(); - break; +#include "gcode/control/M400.h" - case 351: // M351: Toggle MS1 MS2 pins directly, S# determines MS1 or MS2, X# sets the pin high/low. - gcode_M351(); - break; +#if HAS_BED_PROBE + #include "gcode/probe/M401_M402.h" +#endif - #endif // HAS_MICROSTEPS +#if ENABLED(FILAMENT_WIDTH_SENSOR) + #include "gcode/sensor/M404.h" + #include "gcode/sensor/M405.h" + #include "gcode/sensor/M406.h" + #include "gcode/sensor/M407.h" +#endif - case 355: // M355 set case light brightness - gcode_M355(); - break; +void quickstop_stepper() { + stepper.quick_stop(); + stepper.synchronize(); + set_current_from_steppers_for_axis(ALL_AXES); + SYNC_PLAN_POSITION_KINEMATIC(); +} - #if ENABLED(DEBUG_GCODE_PARSER) - case 800: - parser.debug(); // GCode Parser Test for M - break; - #endif +#if HAS_LEVELING + #include "gcode/calibrate/M420.h" + #include "gcode/calibrate/M421.h" +#endif - #if ENABLED(I2C_POSITION_ENCODERS) +#if HAS_M206_COMMAND + #include "gcode/geometry/M428.h" +#endif - case 860: // M860 Report encoder module position - gcode_M860(); - break; +#include "gcode/eeprom/M500.h" +#include "gcode/eeprom/M501.h" +#include "gcode/eeprom/M502.h" +#if DISABLED(DISABLE_M503) + #include "gcode/eeprom/M503.h" +#endif - case 861: // M861 Report encoder module status - gcode_M861(); - break; +#if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) + #include "gcode/config/M540.h" +#endif - case 862: // M862 Perform axis test - gcode_M862(); - break; +#if HAS_BED_PROBE + #include "gcode/probe/M851.h" +#endif - case 863: // M863 Calibrate steps/mm - gcode_M863(); - break; +#if ENABLED(ADVANCED_PAUSE_FEATURE) + #include "gcode/feature/pause/M600.h" +#endif - case 864: // M864 Change module address - gcode_M864(); - break; +#if ENABLED(MK2_MULTIPLEXER) + #include "gcode/feature/snmm/M702.h" +#endif - case 865: // M865 Check module firmware version - gcode_M865(); - break; +#if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) + #include "gcode/control/M605.h" +#endif - case 866: // M866 Report axis error count - gcode_M866(); - break; +#if ENABLED(LIN_ADVANCE) + #include "gcode/feature/advance/M900.h" +#endif - case 867: // M867 Toggle error correction - gcode_M867(); - break; +#if ENABLED(HAVE_TMC2130) + #include "feature/tmc2130.h" + #include "gcode/feature/trinamic/M906.h" + #include "gcode/feature/trinamic/M911.h" + #include "gcode/feature/trinamic/M912.h" + #if ENABLED(HYBRID_THRESHOLD) + #include "gcode/feature/trinamic/M913.h" + #endif + #if ENABLED(SENSORLESS_HOMING) + #include "gcode/feature/trinamic/M914.h" + #endif +#endif - case 868: // M868 Set error correction threshold - gcode_M868(); - break; +#include "gcode/feature/digipot/M907.h" - case 869: // M869 Report axis error - gcode_M869(); - break; +#if HAS_DIGIPOTSS || ENABLED(DAC_STEPPER_CURRENT) + #include "gcode/feature/digipot/M908.h" + #if ENABLED(DAC_STEPPER_CURRENT) // As with Printrbot RevF + #include "gcode/feature/digipot/M909.h" + #include "gcode/feature/digipot/M910.h" + #endif +#endif - #endif // I2C_POSITION_ENCODERS +#if HAS_MICROSTEPS + #include "gcode/control/M350.h" + #include "gcode/control/M351.h" +#endif - case 999: // M999: Restart after being Stopped - gcode_M999(); - break; - } - break; +#include "gcode/feature/caselight/M355.h" - case 'T': - gcode_T(parser.codenum); - break; +#if ENABLED(MIXING_EXTRUDER) + #include "gcode/feature/mixing/M163.h" + #if MIXING_VIRTUAL_TOOLS > 1 + #include "gcode/feature/mixing/M164.h" + #endif + #if ENABLED(DIRECT_MIXING_IN_G1) + #include "gcode/feature/mixing/M165.h" + #endif +#endif - default: parser.unknown_command_error(); - } +#include "gcode/control/M999.h" - KEEPALIVE_STATE(NOT_BUSY); +#include "gcode/control/T.h" - ok_to_send(); -} +#include "gcode/process_next_command.h" /** * Send a "Resend: nnn" message to the host to @@ -12451,178 +4730,6 @@ void prepare_move_to_destination() { set_current_to_destination(); } -#if ENABLED(ARC_SUPPORT) - - #if N_ARC_CORRECTION < 1 - #undef N_ARC_CORRECTION - #define N_ARC_CORRECTION 1 - #endif - - /** - * Plan an arc in 2 dimensions - * - * The arc is approximated by generating many small linear segments. - * The length of each segment is configured in MM_PER_ARC_SEGMENT (Default 1mm) - * Arcs should only be made relatively large (over 5mm), as larger arcs with - * larger segments will tend to be more efficient. Your slicer should have - * options for G2/G3 arc generation. In future these options may be GCode tunable. - */ - void plan_arc( - float logical[XYZE], // Destination position - float *offset, // Center of rotation relative to current_position - uint8_t clockwise // Clockwise? - ) { - #if ENABLED(CNC_WORKSPACE_PLANES) - AxisEnum p_axis, q_axis, l_axis; - switch (workspace_plane) { - case PLANE_XY: p_axis = X_AXIS; q_axis = Y_AXIS; l_axis = Z_AXIS; break; - case PLANE_ZX: p_axis = Z_AXIS; q_axis = X_AXIS; l_axis = Y_AXIS; break; - case PLANE_YZ: p_axis = Y_AXIS; q_axis = Z_AXIS; l_axis = X_AXIS; break; - } - #else - constexpr AxisEnum p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS; - #endif - - // Radius vector from center to current location - float r_P = -offset[0], r_Q = -offset[1]; - - const float radius = HYPOT(r_P, r_Q), - center_P = current_position[p_axis] - r_P, - center_Q = current_position[q_axis] - r_Q, - rt_X = logical[p_axis] - center_P, - rt_Y = logical[q_axis] - center_Q, - linear_travel = logical[l_axis] - current_position[l_axis], - extruder_travel = logical[E_AXIS] - current_position[E_AXIS]; - - // CCW angle of rotation between position and target from the circle center. Only one atan2() trig computation required. - float angular_travel = ATAN2(r_P * rt_Y - r_Q * rt_X, r_P * rt_X + r_Q * rt_Y); - if (angular_travel < 0) angular_travel += RADIANS(360); - if (clockwise) angular_travel -= RADIANS(360); - - // Make a circle if the angular rotation is 0 and the target is current position - if (angular_travel == 0 && current_position[p_axis] == logical[p_axis] && current_position[q_axis] == logical[q_axis]) - angular_travel = RADIANS(360); - - const float mm_of_travel = HYPOT(angular_travel * radius, FABS(linear_travel)); - if (mm_of_travel < 0.001) return; - - uint16_t segments = FLOOR(mm_of_travel / (MM_PER_ARC_SEGMENT)); - if (segments == 0) segments = 1; - - /** - * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, - * and phi is the angle of rotation. Based on the solution approach by Jens Geisler. - * r_T = [cos(phi) -sin(phi); - * sin(phi) cos(phi)] * r ; - * - * For arc generation, the center of the circle is the axis of rotation and the radius vector is - * defined from the circle center to the initial position. Each line segment is formed by successive - * vector rotations. This requires only two cos() and sin() computations to form the rotation - * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since - * all double numbers are single precision on the Arduino. (True double precision will not have - * round off issues for CNC applications.) Single precision error can accumulate to be greater than - * tool precision in some cases. Therefore, arc path correction is implemented. - * - * Small angle approximation may be used to reduce computation overhead further. This approximation - * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words, - * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large - * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for - * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an - * issue for CNC machines with the single precision Arduino calculations. - * - * This approximation also allows plan_arc to immediately insert a line segment into the planner - * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied - * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead. - * This is important when there are successive arc motions. - */ - // Vector rotation matrix values - float arc_target[XYZE]; - const float theta_per_segment = angular_travel / segments, - linear_per_segment = linear_travel / segments, - extruder_per_segment = extruder_travel / segments, - sin_T = theta_per_segment, - cos_T = 1 - 0.5 * sq(theta_per_segment); // Small angle approximation - - // Initialize the linear axis - arc_target[l_axis] = current_position[l_axis]; - - // Initialize the extruder axis - arc_target[E_AXIS] = current_position[E_AXIS]; - - const float fr_mm_s = MMS_SCALED(feedrate_mm_s); - - millis_t next_idle_ms = millis() + 200UL; - - #if N_ARC_CORRECTION > 1 - int8_t arc_recalc_count = N_ARC_CORRECTION; - #endif - - for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times - - thermalManager.manage_heater(); - if (ELAPSED(millis(), next_idle_ms)) { - next_idle_ms = millis() + 200UL; - idle(); - } - - #if N_ARC_CORRECTION > 1 - if (--arc_recalc_count) { - // Apply vector rotation matrix to previous r_P / 1 - const float r_new_Y = r_P * sin_T + r_Q * cos_T; - r_P = r_P * cos_T - r_Q * sin_T; - r_Q = r_new_Y; - } - else - #endif - { - #if N_ARC_CORRECTION > 1 - arc_recalc_count = N_ARC_CORRECTION; - #endif - - // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. - // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - // To reduce stuttering, the sin and cos could be computed at different times. - // For now, compute both at the same time. - const float cos_Ti = cos(i * theta_per_segment), sin_Ti = sin(i * theta_per_segment); - r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti; - r_Q = -offset[0] * sin_Ti - offset[1] * cos_Ti; - } - - // Update arc_target location - arc_target[p_axis] = center_P + r_P; - arc_target[q_axis] = center_Q + r_Q; - arc_target[l_axis] += linear_per_segment; - arc_target[E_AXIS] += extruder_per_segment; - - clamp_to_software_endstops(arc_target); - - planner.buffer_line_kinematic(arc_target, fr_mm_s, active_extruder); - } - - // Ensure last segment arrives at target location. - planner.buffer_line_kinematic(logical, fr_mm_s, active_extruder); - - // As far as the parser is concerned, the position is now == target. In reality the - // motion control system might still be processing the action and the real tool position - // in any intermediate location. - set_current_to_destination(); - } // plan_arc - -#endif // ARC_SUPPORT - -#if ENABLED(BEZIER_CURVE_SUPPORT) - - void plan_cubic_move(const float offset[4]) { - cubic_b_spline(current_position, destination, offset, MMS_SCALED(feedrate_mm_s), active_extruder); - - // As far as the parser is concerned, the position is now == destination. In reality the - // motion control system might still be processing the action and the real tool position - // in any intermediate location. - set_current_to_destination(); - } - -#endif // BEZIER_CURVE_SUPPORT - #if ENABLED(USE_CONTROLLER_FAN) void controllerFan() { @@ -12787,63 +4894,6 @@ void prepare_move_to_destination() { #endif // FILAMENT_RUNOUT_SENSOR -#if ENABLED(FAST_PWM_FAN) - - void setPwmFrequency(uint8_t pin, int val) { - val &= 0x07; - switch (digitalPinToTimer(pin)) { - #ifdef TCCR0A - #if !AVR_AT90USB1286_FAMILY - case TIMER0A: - #endif - case TIMER0B: - //_SET_CS(0, val); - break; - #endif - #ifdef TCCR1A - case TIMER1A: - case TIMER1B: - //_SET_CS(1, val); - break; - #endif - #ifdef TCCR2 - case TIMER2: - case TIMER2: - _SET_CS(2, val); - break; - #endif - #ifdef TCCR2A - case TIMER2A: - case TIMER2B: - _SET_CS(2, val); - break; - #endif - #ifdef TCCR3A - case TIMER3A: - case TIMER3B: - case TIMER3C: - _SET_CS(3, val); - break; - #endif - #ifdef TCCR4A - case TIMER4A: - case TIMER4B: - case TIMER4C: - _SET_CS(4, val); - break; - #endif - #ifdef TCCR5A - case TIMER5A: - case TIMER5B: - case TIMER5C: - _SET_CS(5, val); - break; - #endif - } - } - -#endif // FAST_PWM_FAN - float calculate_volumetric_multiplier(const float diameter) { if (!volumetric_enabled || diameter == 0) return 1.0; return 1.0 / (M_PI * sq(diameter * 0.5)); @@ -12880,106 +4930,6 @@ void disable_all_steppers() { disable_e_steppers(); } -#if ENABLED(HAVE_TMC2130) - - void automatic_current_control(TMC2130Stepper &st, String axisID) { - // Check otpw even if we don't use automatic control. Allows for flag inspection. - const bool is_otpw = st.checkOT(); - - // Report if a warning was triggered - static bool previous_otpw = false; - if (is_otpw && !previous_otpw) { - char timestamp[10]; - duration_t elapsed = print_job_timer.duration(); - const bool has_days = (elapsed.value > 60*60*24L); - (void)elapsed.toDigital(timestamp, has_days); - SERIAL_ECHO(timestamp); - SERIAL_ECHOPGM(": "); - SERIAL_ECHO(axisID); - SERIAL_ECHOLNPGM(" driver overtemperature warning!"); - } - previous_otpw = is_otpw; - - #if CURRENT_STEP > 0 && ENABLED(AUTOMATIC_CURRENT_CONTROL) - // Return if user has not enabled current control start with M906 S1. - if (!auto_current_control) return; - - /** - * Decrease current if is_otpw is true. - * Bail out if driver is disabled. - * Increase current if OTPW has not been triggered yet. - */ - uint16_t current = st.getCurrent(); - if (is_otpw) { - st.setCurrent(current - CURRENT_STEP, R_SENSE, HOLD_MULTIPLIER); - #if ENABLED(REPORT_CURRENT_CHANGE) - SERIAL_ECHO(axisID); - SERIAL_ECHOPAIR(" current decreased to ", st.getCurrent()); - #endif - } - - else if (!st.isEnabled()) - return; - - else if (!is_otpw && !st.getOTPW()) { - current += CURRENT_STEP; - if (current <= AUTO_ADJUST_MAX) { - st.setCurrent(current, R_SENSE, HOLD_MULTIPLIER); - #if ENABLED(REPORT_CURRENT_CHANGE) - SERIAL_ECHO(axisID); - SERIAL_ECHOPAIR(" current increased to ", st.getCurrent()); - #endif - } - } - SERIAL_EOL(); - #endif - } - - void checkOverTemp() { - static millis_t next_cOT = 0; - if (ELAPSED(millis(), next_cOT)) { - next_cOT = millis() + 5000; - #if ENABLED(X_IS_TMC2130) - automatic_current_control(stepperX, "X"); - #endif - #if ENABLED(Y_IS_TMC2130) - automatic_current_control(stepperY, "Y"); - #endif - #if ENABLED(Z_IS_TMC2130) - automatic_current_control(stepperZ, "Z"); - #endif - #if ENABLED(X2_IS_TMC2130) - automatic_current_control(stepperX2, "X2"); - #endif - #if ENABLED(Y2_IS_TMC2130) - automatic_current_control(stepperY2, "Y2"); - #endif - #if ENABLED(Z2_IS_TMC2130) - automatic_current_control(stepperZ2, "Z2"); - #endif - #if ENABLED(E0_IS_TMC2130) - automatic_current_control(stepperE0, "E0"); - #endif - #if ENABLED(E1_IS_TMC2130) - automatic_current_control(stepperE1, "E1"); - #endif - #if ENABLED(E2_IS_TMC2130) - automatic_current_control(stepperE2, "E2"); - #endif - #if ENABLED(E3_IS_TMC2130) - automatic_current_control(stepperE3, "E3"); - #endif - #if ENABLED(E4_IS_TMC2130) - automatic_current_control(stepperE4, "E4"); - #endif - #if ENABLED(E4_IS_TMC2130) - automatic_current_control(stepperE4); - #endif - } - } - -#endif // HAVE_TMC2130 - /** * Manage several activities: * - Check for Filament Runout @@ -13155,7 +5105,7 @@ void manage_inactivity(bool ignore_stepper_queue/*=false*/) { #endif #if ENABLED(HAVE_TMC2130) - checkOverTemp(); + tmc2130_checkOverTemp(); #endif planner.check_axes_activity(); @@ -13383,7 +5333,7 @@ void setup() { #if SPINDLE_DIR_CHANGE OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR ? 255 : 0); // init rotation to clockwise (M3) #endif - #if ENABLED(SPINDLE_LASER_PWM) + #if ENABLED(SPINDLE_LASER_PWM) && defined(SPINDLE_LASER_PWM_PIN) && SPINDLE_LASER_PWM_PIN >= 0 SET_OUTPUT(SPINDLE_LASER_PWM_PIN); analogWrite(SPINDLE_LASER_PWM_PIN, SPINDLE_LASER_PWM_INVERT ? 255 : 0); // set to lowest speed #endif diff --git a/Marlin/src/Marlin.h b/Marlin/src/Marlin.h index 590450ea1..53b2eb9ab 100644 --- a/Marlin/src/Marlin.h +++ b/Marlin/src/Marlin.h @@ -19,33 +19,27 @@ * along with this program. If not, see . * */ -#ifndef MARLIN_H -#define MARLIN_H +#ifndef __MARLIN_H__ +#define __MARLIN_H__ #include #include #include -#include -#include -#include "MarlinConfig.h" +#include "inc/MarlinConfig.h" #ifdef DEBUG_GCODE_PARSER - #include "gcode.h" + #include "gcode/parser.h" #endif -#include "src/HAL/HAL.h" - -#include "enum.h" -#include "types.h" -#include "utility.h" -#include "serial.h" #if ENABLED(PRINTCOUNTER) - #include "printcounter.h" + #include "module/printcounter.h" #else - #include "stopwatch.h" + #include "libs/stopwatch.h" #endif +void stop(); + void idle( #if ENABLED(ADVANCED_PAUSE_FEATURE) bool no_stepper_sleep = false // pass true to keep steppers from disabling on timeout @@ -195,10 +189,6 @@ void clear_command_queue(); extern millis_t previous_cmd_ms; inline void refresh_cmd_timeout() { previous_cmd_ms = millis(); } -#if ENABLED(FAST_PWM_FAN) - void setPwmFrequency(uint8_t pin, int val); -#endif - /** * Feedrate scaling and conversion */ @@ -476,4 +466,4 @@ FORCE_INLINE bool position_is_reachable_xy(const float &lx, const float &ly) { return position_is_reachable_raw_xy(RAW_X_POSITION(lx), RAW_Y_POSITION(ly)); } -#endif // MARLIN_H +#endif // __MARLIN_H__