diff --git a/Marlin/src/gcode/calibrate/G34_M422.cpp b/Marlin/src/gcode/calibrate/G34_M422.cpp index 6c367ad882d1..9d2126eec69e 100644 --- a/Marlin/src/gcode/calibrate/G34_M422.cpp +++ b/Marlin/src/gcode/calibrate/G34_M422.cpp @@ -68,6 +68,7 @@ * I Number of test iterations. If omitted, Z_STEPPER_ALIGN_ITERATIONS. (1-30) * T Target Accuracy factor. If omitted, Z_STEPPER_ALIGN_ACC. (0.01-1.0) * A Provide an Amplification value. If omitted, Z_STEPPER_ALIGN_AMP. (0.5-2.0) + * E Stow or raise the probe after probing. 0=raise (default), 1=stow. * R Recalculate points based on current probe offsets * * Example: diff --git a/Marlin/src/gcode/probe/G30.cpp b/Marlin/src/gcode/probe/G30.cpp index 463a4cd83665..0158cdd12295 100644 --- a/Marlin/src/gcode/probe/G30.cpp +++ b/Marlin/src/gcode/probe/G30.cpp @@ -65,8 +65,10 @@ void GcodeSuite::G30() { if (probe.can_reach(probepos)) { - // Disable leveling so the planner won't mess with us - TERN_(HAS_LEVELING, set_bed_leveling_enabled(false)); + #if HAS_LEVELING + // Temporarily disable leveling so the planner won't mess with us + TEMPORARY_BED_LEVELING_STATE(false); + #endif // Disable feedrate scaling so movement speeds are correct remember_feedrate_scaling_off(); @@ -117,7 +119,7 @@ void GcodeSuite::G30() { LCD_MESSAGE(MSG_ZPROBE_OUT); } - probe.use_probing_tool(false); + probe.use_probing_tool(false); } #endif // HAS_BED_PROBE diff --git a/Marlin/src/gcode/probe/G38.cpp b/Marlin/src/gcode/probe/G38.cpp index cd10b7295d56..e52e89a60cd9 100644 --- a/Marlin/src/gcode/probe/G38.cpp +++ b/Marlin/src/gcode/probe/G38.cpp @@ -30,41 +30,11 @@ #include "../../module/motion.h" #include "../../module/planner.h" #include "../../module/probe.h" +#include "../../feature/bedlevel/bedlevel.h" +#include "../../lcd/marlinui.h" -probe_target_t G38_move{0}; - -inline void G38_single_probe(const uint8_t move_value) { - endstops.enable(true); - G38_move.type = move_value; - prepare_line_to_destination(); - planner.synchronize(); - G38_move.type = 0; - endstops.hit_on_purpose(); - set_current_from_steppers_for_axis(ALL_AXES_ENUM); - sync_plan_position(); -} - -/** - * Handle G38.N where N is the sub-code for the type of probe: - * 2 - Probe toward workpiece, stop on contact, signal error if failure - * 3 - Probe toward workpiece, stop on contact - * 4 - Probe away from workpiece, stop on contact break, signal error if failure - * 5 - Probe away from workpiece, stop on contact break - */ -FORCE_INLINE bool G38_run_probe() { - - bool G38_pass_fail = false; - - #if MULTIPLE_PROBING > 1 - // Get direction of move and retract - xyz_float_t retract_mm; - LOOP_NUM_AXES(i) { - const float dist = destination[i] - current_position[i]; - retract_mm[i] = ABS(dist) < G38_MINIMUM_MOVE ? 0 : home_bump_mm((AxisEnum)i) * (dist > 0 ? -1 : 1); - } - #endif - planner.synchronize(); // Wait until the machine is idle +inline bool G38_run_probe(const ProbePtRaise raise_after) { // Move flag value #if ENABLED(G38_PROBE_AWAY) @@ -73,33 +43,34 @@ FORCE_INLINE bool G38_run_probe() { constexpr uint8_t move_value = 1; #endif - G38_move.triggered = false; - - // Move until destination reached or target hit - G38_single_probe(move_value); - - if (G38_move.triggered) { - - G38_pass_fail = true; - - #if MULTIPLE_PROBING > 1 - // Move away by the retract distance - destination = current_position + retract_mm; - endstops.enable(false); - prepare_line_to_destination(); - planner.synchronize(); - - REMEMBER(fr, feedrate_mm_s, feedrate_mm_s * 0.25); - - // Bump the target more slowly - destination -= retract_mm * 2; + const xyz_pos_t measured = probe.probe_safely(destination, raise_after, move_value, 0, true, true, Z_TWEEN_SAFE_CLEARANCE, true); + + LOOP_NUM_AXES(a) { + if (isnan(measured[a])) return true; + } - G38_single_probe(move_value); - #endif + // Report a good probe result in machine coordinate system to the host and LCD + SString<30> msg( + F("Machine X:"), p_float_t(measured.x, 2), + F(" Y:"), p_float_t(measured.y, 2), + F(" Z:"), p_float_t(measured.z, 3) + ); + msg.echoln(); + TERN_(VERBOSE_SINGLE_PROBE, ui.set_status(msg)); + + // If the probe is stowed, move the nozzle to the position of the probe + const xyz_pos_t offs = DIFF_TERN(HAS_HOTEND_OFFSET, probe.offset, hotend_offset[active_extruder]); + if ((!endstops.z_probe_enabled) && (probe.offset.z >= TERN0(HAS_HOTEND_OFFSET, hotend_offset[active_extruder].z))) { + if ((!NEAR_ZERO(offs.x)) || (!NEAR_ZERO(offs.y)) || offs.z > 0.0f) { + do_z_clearance_by(Z_TWEEN_SAFE_CLEARANCE); + } + destination = measured; + do_blocking_move_to(destination); + planner.synchronize(); } - endstops.not_homing(); - return G38_pass_fail; + report_current_position(); + return false; } /** @@ -112,26 +83,46 @@ FORCE_INLINE bool G38_run_probe() { * * G38.4 - Probe away from workpiece, stop on contact break, signal error if failure * G38.5 - Probe away from workpiece, stop on contact break + * + * Parameters: + * + * X Probe X position (default current X) + * Y Probe Y position (default current Y) + * Z Probe Z position (default current Z) + * S Stow the probe after probing (default: 0) */ void GcodeSuite::G38(const int8_t subcode) { // Get X Y Z E F get_destination_from_command(); + + probe.use_probing_tool(); + + #if HAS_LEVELING + // Temporarily disable leveling so the planner won't mess with us + TEMPORARY_BED_LEVELING_STATE(false); + #endif remember_feedrate_scaling_off(); + // Raise after based on the 'S' parameter + const ProbePtRaise raise_after = parser.boolval('S', false) ? PROBE_PT_STOW : PROBE_PT_NONE; + const bool error_on_fail = TERN(G38_PROBE_AWAY, !TEST(subcode, 0), subcode == 2); // If any axis has enough movement, do the move - LOOP_NUM_AXES(i) + LOOP_NUM_AXES(i) { if (ABS(destination[i] - current_position[i]) >= G38_MINIMUM_MOVE) { if (!parser.seenval('F')) feedrate_mm_s = homing_feedrate((AxisEnum)i); // If G38.2 fails throw an error - if (!G38_run_probe() && error_on_fail) SERIAL_ERROR_MSG("Failed to reach target"); + if (G38_run_probe(raise_after) && error_on_fail) { + SERIAL_ERROR_MSG("Failed to reach target"); + } break; } - + } restore_feedrate_and_scaling(); + probe.use_probing_tool(false); } #endif // G38_PROBE_TARGET diff --git a/Marlin/src/module/endstops.cpp b/Marlin/src/module/endstops.cpp index 5b7be5d00c1b..437d331b08e3 100644 --- a/Marlin/src/module/endstops.cpp +++ b/Marlin/src/module/endstops.cpp @@ -426,7 +426,7 @@ void Endstops::update() { #if ENABLED(G38_PROBE_TARGET) // For G38 moves check the probe's pin for ALL movement - if (G38_move.type) UPDATE_LIVE_STATE(Z, TERN(USE_Z_MIN_PROBE, MIN_PROBE, MIN)); + if (probe.G38_move.type) UPDATE_LIVE_STATE(Z, TERN(USE_Z_MIN_PROBE, MIN_PROBE, MIN)); #endif #if ENABLED(CALIBRATION_GCODE) @@ -666,8 +666,8 @@ void Endstops::update() { #if ENABLED(G38_PROBE_TARGET) // For G38 moves check the probe's pin for ALL movement - if (G38_move.type && TEST_ENDSTOP(Z_MIN_PROBE) == TERN1(G38_PROBE_AWAY, (G38_move.type < 4))) { - G38_move.triggered = true; + if (probe.G38_move.type && TEST_ENDSTOP(Z_MIN_PROBE) == TERN1(G38_PROBE_AWAY, (probe.G38_move.type < 4))) { + probe.G38_move.triggered = true; #define _G38_SET(Q) | (AXIS_IS_MOVING(Q) << _AXIS(Q)) #define _G38_RESP(Q) if (moving[_AXIS(Q)]) { _ENDSTOP_HIT(Q, ENDSTOP); planner.endstop_triggered(_AXIS(Q)); } const Flags moving = { uvalue_t(NUM_AXES)(0 MAIN_AXIS_MAP(_G38_SET)) }; diff --git a/Marlin/src/module/endstops.h b/Marlin/src/module/endstops.h index 17715de1dade..23c7e1146230 100644 --- a/Marlin/src/module/endstops.h +++ b/Marlin/src/module/endstops.h @@ -319,5 +319,4 @@ class TemporaryGlobalEndstopsState { uint8_t type; // Flag to tell the ISR the type of G38 in progress; 0 for NONE. bool triggered; // Flag from the ISR to indicate the endstop changed } probe_target_t; - extern probe_target_t G38_move; #endif diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp index 3a8bb2b6c6a0..c418d11255c5 100644 --- a/Marlin/src/module/probe.cpp +++ b/Marlin/src/module/probe.cpp @@ -68,6 +68,10 @@ #endif #endif +#if ENABLED(G38_PROBE_TARGET) + #include "planner.h" +#endif + #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING) #include "../feature/backlash.h" #endif @@ -99,6 +103,8 @@ #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) #include "../core/debug_out.h" + + Probe probe; xyz_pos_t Probe::offset; // Initialized by settings.load @@ -109,6 +115,9 @@ xyz_pos_t Probe::offset; // Initialized by settings.load constexpr xy_pos_t Probe::offset_xy; #endif +#if ENABLED(G38_PROBE_TARGET) + probe_target_t Probe::G38_move{0}; +#endif #if ENABLED(SENSORLESS_PROBING) Probe::sense_bool_t Probe::test_sensitivity = { true, true, true }; #endif @@ -417,7 +426,9 @@ FORCE_INLINE void probe_specific_action(const bool deploy) { #if ENABLED(SOLENOID_PROBE) #if HAS_SOLENOID_1 + if (!deploy) endstops.enable_z_probe(false); WRITE(SOL1_PIN, deploy); + if (deploy) safe_delay(500); #endif #elif ENABLED(MAGLEV4) @@ -605,27 +616,29 @@ bool Probe::set_deployed(const bool deploy, const bool no_return/*=false*/) { * @brief Move down until the probe triggers or the low limit is reached * Used by run_z_probe to do a single Z probe move. * - * @param z Z destination - * @param fr_mm_s Feedrate in mm/s + * @param pos probing destination + * @param fr_mm_s Feedrate in mm/s + * @param move_value G38 subcode * @return true to indicate an error * - * @details Used by run_z_probe to get each bed Z height measurement. + * @details Used by run_probe to get each bed Z height measurement. * Sets current_position.z to the height where the probe triggered * (according to the Z stepper count). The float Z is propagated * back to the planner.position to preempt any rounding error. * * @return TRUE if the probe failed to trigger. */ -bool Probe::probe_down_to_z(const float z, const feedRate_t fr_mm_s) { - DEBUG_SECTION(log_probe, "Probe::probe_down_to_z", DEBUGGING(LEVELING)); - - #if ALL(HAS_HEATED_BED, WAIT_FOR_BED_HEATER) - thermalManager.wait_for_bed_heating(); - #endif +bool Probe::probe_to_target(const xyz_pos_t &pos, const feedRate_t fr_mm_s, const uint8_t move_value) { + DEBUG_SECTION(log_probe, "Probe::probe_to_tagret", DEBUGGING(LEVELING)); + if (TERN1(G38_PROBE_TARGET, move_value == 0)) { + #if ALL(HAS_HEATED_BED, WAIT_FOR_BED_HEATER) + thermalManager.wait_for_bed_heating(); + #endif - #if ALL(HAS_TEMP_HOTEND, WAIT_FOR_HOTEND) - thermalManager.wait_for_hotend_heating(active_extruder); - #endif + #if ALL(HAS_TEMP_HOTEND, WAIT_FOR_HOTEND) + thermalManager.wait_for_hotend_heating(active_extruder); + #endif + } #if ENABLED(BLTOUCH) // Ensure the BLTouch is deployed. (Does nothing if already deployed.) @@ -633,10 +646,15 @@ bool Probe::probe_down_to_z(const float z, const feedRate_t fr_mm_s) { if (TERN(MEASURE_BACKLASH_WHEN_PROBING, true, !bltouch.high_speed_mode) && bltouch.deploy()) return true; #endif - #if HAS_Z_SERVO_PROBE && (ENABLED(Z_SERVO_INTERMEDIATE_STOW) || defined(Z_SERVO_MEASURE_ANGLE)) probe_specific_action(true); // Always re-deploy in this case #endif + #if ENABLED(SOLENOID_PROBE) + if (deploy()) { + endstops.not_homing(); + return true; + } + #endif // Disable stealthChop if used. Enable diag1 pin on driver. #if ENABLED(SENSORLESS_PROBING) @@ -664,17 +682,34 @@ bool Probe::probe_down_to_z(const float z, const feedRate_t fr_mm_s) { TERN_(HAS_QUIET_PROBING, set_devices_paused_for_probing(true)); - // Move down until the probe is triggered - do_blocking_move_to_z(z, fr_mm_s); - - // Check to see if the probe was triggered - const bool probe_triggered = ( - #if HAS_DELTA_SENSORLESS_PROBING - PROBE_TRIGGERED() - #else - TEST(endstops.trigger_state(), Z_MIN_PROBE) - #endif - ); + bool probe_triggered; + #if ENABLED(G38_PROBE_TARGET) + if (move_value > 0) { + G38_move.triggered = false; + G38_move.type = move_value; + endstops.enable(true); + destination = pos; + // Move down until the probe is triggered + prepare_line_to_destination(); + planner.synchronize(); + probe_triggered = G38_move.triggered; + endstops.not_homing(); + G38_move.type = 0; + } + else + #endif + { + // Move down until the probe is triggered + do_blocking_move_to_z(pos.z, fr_mm_s); + // Check to see if the probe was triggered + probe_triggered = ( + #if HAS_DELTA_SENSORLESS_PROBING + PROBE_TRIGGERED() + #else + TEST(endstops.trigger_state(), Z_MIN_PROBE) + #endif + ); + } // Offset sensorless probing #if HAS_DELTA_SENSORLESS_PROBING @@ -710,15 +745,15 @@ bool Probe::probe_down_to_z(const float z, const feedRate_t fr_mm_s) { return true; // Stow in LOW SPEED MODE on every trigger #endif - #if ALL(HAS_Z_SERVO_PROBE, Z_SERVO_INTERMEDIATE_STOW) + #if ENABLED(SOLENOID_PROBE) || ALL(HAS_Z_SERVO_PROBE, Z_SERVO_INTERMEDIATE_STOW) probe_specific_action(false); // Always stow #endif // Clear endstop flags endstops.hit_on_purpose(); - // Get Z where the steppers were interrupted - set_current_from_steppers_for_axis(Z_AXIS); + // Get position where the steppers were interrupted + set_current_from_steppers_for_axis(ALL_AXES_ENUM); // Tell the planner where we actually are sync_plan_position(); @@ -772,34 +807,35 @@ bool Probe::probe_down_to_z(const float z, const feedRate_t fr_mm_s) { * @param sanity_check Flag to compare the probe result with the expected result * based on the probe Z offset. If the result is too far away * (more than Z_PROBE_ERROR_TOLERANCE too early) then throw an error. - * @param z_min_point Override the minimum probing height (-2mm), to allow deeper probing. - * @param z_clearance Z clearance to apply on probe failure. + * @param target minimum probing height, to allow deeper probing. + * @param z_clearance Z clearance to apply on probe failure. * * @return The Z position of the bed at the current XY or NAN on error. */ -float Probe::run_z_probe(const bool sanity_check/*=true*/, const float z_min_point/*=Z_PROBE_LOW_POINT*/, const float z_clearance/*=Z_TWEEN_SAFE_CLEARANCE*/) { - DEBUG_SECTION(log_probe, "Probe::run_z_probe", DEBUGGING(LEVELING)); - - const float zoffs = SUM_TERN(HAS_HOTEND_OFFSET, -offset.z, hotend_offset[active_extruder].z); +xyz_pos_t Probe::run_probe(const bool sanity_check/*=true*/, const xyz_pos_t &target, const float z_clearance/*=Z_TWEEN_SAFE_CLEARANCE*/, const uint8_t move_value) { + DEBUG_SECTION(log_probe, "Probe::run_probe", DEBUGGING(LEVELING)); - auto try_to_probe = [&](PGM_P const plbl, const float z_probe_low_point, const feedRate_t fr_mm_s, const bool scheck) -> bool { + const xyz_pos_t nan_pos = {NUM_AXIS_LIST(NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN)}; + const xyz_pos_t offs = SUM_TERN(HAS_HOTEND_OFFSET, -offset, hotend_offset[active_extruder]); + + auto try_to_probe = [&](PGM_P const plbl, const xyz_pos_t target_point, const feedRate_t fr_mm_s, const bool scheck, const uint8_t move_value) -> bool { constexpr float error_tolerance = Z_PROBE_ERROR_TOLERANCE; if (DEBUGGING(LEVELING)) { DEBUG_ECHOPGM_P(plbl); - DEBUG_ECHOLNPGM("> try_to_probe(..., ", z_probe_low_point, ", ", fr_mm_s, ", ...)"); + DEBUG_ECHOLNPGM("> try_to_probe(..., ", target_point, ", ", fr_mm_s, ", ...)"); } // Tare the probe, if supported if (TERN0(PROBE_TARE, tare())) return true; - // Do a first probe at the fast speed - const bool probe_fail = probe_down_to_z(z_probe_low_point, fr_mm_s), // No probe trigger? - early_fail = (scheck && current_position.z > zoffs + error_tolerance); // Probe triggered too high? + // Probe and ckeck for failure + const bool probe_fail = probe_to_target(target_point, fr_mm_s, move_value), // No probe trigger? + early_fail = (scheck && (!probe_fail) && current_position.z > target_point.z + error_tolerance); // Probe triggered too high? #if ENABLED(DEBUG_LEVELING_FEATURE) if (DEBUGGING(LEVELING) && (probe_fail || early_fail)) { DEBUG_ECHOPGM(" Probe fail! - "); if (probe_fail) DEBUG_ECHOLNPGM("No trigger."); - if (early_fail) DEBUG_ECHOLNPGM("Triggered early (above ", zoffs + error_tolerance, "mm)"); + if (early_fail) DEBUG_ECHOLNPGM("Triggered early (above ", target_point.z + error_tolerance, "mm)"); } #else UNUSED(plbl); @@ -809,33 +845,81 @@ float Probe::run_z_probe(const bool sanity_check/*=true*/, const float z_min_poi // Stop the probe before it goes too low to prevent damage. // For known Z probe below the expected trigger point, otherwise -10mm lower. - const float z_probe_low_point = zoffs + z_min_point -float((!axis_is_trusted(Z_AXIS)) * 10); - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Probe Low Point: ", z_probe_low_point); + const xyz_pos_t probe_target_point = {NUM_AXIS_LIST( + offs.x + target.x - float(!axis_is_trusted(X_AXIS) * 10.0f), + offs.y + target.y - float(!axis_is_trusted(Y_AXIS) * 10.0f), + offs.z + target.z - float(!axis_is_trusted(Z_AXIS) * 10.0f), + offs.i + target.i - float(!axis_is_trusted(I_AXIS) * 10.0f), + offs.j + target.j - float(!axis_is_trusted(J_AXIS) * 10.0f), + offs.k + target.k - float(!axis_is_trusted(K_AXIS) * 10.0f), + offs.u + target.u - float(!axis_is_trusted(U_AXIS) * 10.0f), + offs.v + target.v - float(!axis_is_trusted(V_AXIS) * 10.0f), + offs.w + target.w - float(!axis_is_trusted(W_AXIS) * 10.0f) + )}; + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Probe Low Point: ", probe_target_point.z); + + #if ENABLED(G38_PROBE_TARGET) + xyz_float_t retract_mm; + LOOP_NUM_AXES(i) { + const float dist = probe_target_point[i] - current_position[i]; + if (i == Z_AXIS) { + retract_mm[i] = ABS(dist) < G38_MINIMUM_MOVE ? 0 : _MAX(home_bump_mm((AxisEnum)i), (Z_CLEARANCE_DEPLOY_PROBE)) * (dist > 0 ? -1 : 1); + } + else { + retract_mm[i] = ABS(dist) < G38_MINIMUM_MOVE ? 0 : home_bump_mm((AxisEnum)i) * (dist > 0 ? -1 : 1); + } + } + #endif // Double-probing does a fast probe followed by a slow probe #if TOTAL_PROBING == 2 - // Attempt to tare the probe - if (TERN0(PROBE_TARE, tare())) return NAN; + if (TERN0(PROBE_TARE, tare())) return nan_pos; // Do a first probe at the fast speed - if (try_to_probe(PSTR("FAST"), z_probe_low_point, z_probe_fast_mm_s, sanity_check)) return NAN; - - const float z1 = DIFF_TERN(HAS_DELTA_SENSORLESS_PROBING, current_position.z, largest_sensorless_adj); - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("1st Probe Z:", z1); + if (try_to_probe(PSTR("FAST"), probe_target_point, move_value > 0 ? feedrate_mm_s : z_probe_fast_mm_s, sanity_check, move_value)) return nan_pos; + xyze_pos_t targ1 = current_position; + #if ENABLED(G38_PROBE_TARGET) + if (move_value > 0) { + const xyze_pos_t targ1 = current_position; + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("1st Probe Z:", targ1.z); + // Move away by the retract distance + destination = current_position + retract_mm; + prepare_line_to_destination(); + planner.synchronize(); + } + else + #endif + { + TERN_(HAS_DELTA_SENSORLESS_PROBING, targ1.z -= largest_sensorless_adj); + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("1st Probe Z:", targ1.z); - // Raise to give the probe clearance - do_z_clearance(z1 + (Z_CLEARANCE_MULTI_PROBE), false); + // Raise to give the probe clearance + do_z_clearance(targ1.z + (Z_CLEARANCE_MULTI_PROBE), false); + } #elif Z_PROBE_FEEDRATE_FAST != Z_PROBE_FEEDRATE_SLOW - - // If the nozzle is well over the travel height then - // move down quickly before doing the slow probe - const float z = (Z_CLEARANCE_DEPLOY_PROBE) + 5.0f + _MAX(zoffs, 0.0f); - if (current_position.z > z) { - // Probe down fast. If the probe never triggered, raise for probe clearance - if (!probe_down_to_z(z, z_probe_fast_mm_s)) - do_z_clearance(z_clearance); + #if ENABLED(G38_PROBE_TARGET) + if (move_value > 0) { + if(!probe_to_target(probe_target_point, feedrate_mm_s, move_value)) { + // Move away by the retract distance + destination = current_position + retract_mm; + prepare_line_to_destination(); + planner.synchronize(); + }; + } + else + #endif + { + // If the nozzle is well over the travel height then + // move down quickly before doing the slow probe + xyz_pos_t probe_pos = current_position; + probe_pos.z = (Z_CLEARANCE_DEPLOY_PROBE) + 5.0f + _MAX(offs.z, 0.0f); + if (current_position.z > probe_pos.z) { + // Probe down fast. If the probe triggered, raise for probe clearance + if (!probe_to_target(probe_pos, z_probe_fast_mm_s, 0)) + do_z_clearance(z_clearance); + } } #endif @@ -855,11 +939,12 @@ float Probe::run_z_probe(const bool sanity_check/*=true*/, const float z_min_poi #endif { // If the probe won't tare, return - if (TERN0(PROBE_TARE, tare())) return true; + if (TERN0(PROBE_TARE, tare())) return nan_pos; + const feedRate_t fr = (TERN0(G38_PROBE_TARGET, (move_value > 0) && (Z_PROBE_FEEDRATE_FAST == Z_PROBE_FEEDRATE_SLOW))) ? feedrate_mm_s : z_probe_slow_mm_s; // Probe downward slowly to find the bed if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Slow Probe:"); - if (try_to_probe(PSTR("SLOW"), z_probe_low_point, z_probe_slow_mm_s, sanity_check)) return NAN; + if (try_to_probe(PSTR("SLOW"), probe_target_point, fr, sanity_check, move_value)) return nan_pos; TERN_(MEASURE_BACKLASH_WHEN_PROBING, backlash.measure_with_probe()); @@ -910,25 +995,27 @@ float Probe::run_z_probe(const bool sanity_check/*=true*/, const float z_min_poi #endif - const float measured_z = probes_z_sum * RECIPROCAL(MULTIPLE_PROBING); + xyz_pos_t measured = current_position; + measured.z = probes_z_sum * RECIPROCAL(MULTIPLE_PROBING); #elif TOTAL_PROBING == 2 - - const float z2 = DIFF_TERN(HAS_DELTA_SENSORLESS_PROBING, current_position.z, largest_sensorless_adj); - - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("2nd Probe Z:", z2, " Discrepancy:", z1 - z2); + xyz_pos_t targ2 = current_position; + if (TERN1(G38_PROBE_TARGET, move_value == 0)) { + TERN_(HAS_DELTA_SENSORLESS_PROBING, targ2.z -= largest_sensorless_adj); + } + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("2nd Probe Z:", targ2.z, " Discrepancy:", targ1.z - targ2.z); // Return a weighted average of the fast and slow probes - const float measured_z = (z2 * 3.0f + z1 * 2.0f) * 0.2f; + const xyz_pos_t measured = (targ2 * 3.0f + targ1 * 2.0f) * 0.2f; #else // Return the single probe result - const float measured_z = current_position.z; + const xyz_pos_t measured = current_position; #endif - - return DIFF_TERN(HAS_HOTEND_OFFSET, measured_z, hotend_offset[active_extruder].z); + + return DIFF_TERN(HAS_HOTEND_OFFSET, measured, hotend_offset[active_extruder]); } #if DO_TOOLCHANGE_FOR_PROBING @@ -954,9 +1041,11 @@ float Probe::run_z_probe(const bool sanity_check/*=true*/, const float z_min_poi #endif /** - * - Move to the given XY + * - Change to probing tool. + * - Adjust for probe offset and hotend offset. + * - Move to the given XY. * - Deploy the probe, if not already deployed - * - Probe the bed, get the Z position + * - Probe the bed, get the Z position according to settings MULTIPLE_PROBING and EXTRA_PROBING * - Depending on the 'stow' flag * - Stow the probe, or * - Raise to the BETWEEN height @@ -970,19 +1059,55 @@ float Probe::run_z_probe(const bool sanity_check/*=true*/, const float z_min_poi */ float Probe::probe_at_point( const float rx, const float ry, - const ProbePtRaise raise_after, // = PROBE_PT_NONE - const uint8_t verbose_level, // = 0 - const bool probe_relative, // = true - const bool sanity_check, // = true + const ProbePtRaise raise_after, // = PROBE_PT_NONE + const uint8_t verbose_level, // = 0 + const bool probe_relative, // = true + const bool sanity_check, // = true const float z_min_point, // = Z_PROBE_LOW_POINT const float z_clearance, // = Z_TWEEN_SAFE_CLEARANCE - const bool raise_after_is_rel // = false + const bool raise_after_is_rel // = false +) { + const xyz_pos_t probe_pos = NUM_AXIS_ARRAY( + rx, ry, z_min_point, + current_position.i, current_position.j, current_position.k, + current_position.u, current_position.v, current_position.w + ); + const xyz_pos_t measured = probe_safely(probe_pos, raise_after, 0, verbose_level, probe_relative, sanity_check, z_clearance, raise_after_is_rel); + return measured.z; +} + +/** + * - Change to probing tool. + * - Adjust for probe offset and hotend offset. + * - Move to the given XY if probe_3d is false. + * - Deploy the probe, if not already deployed + * - Probe the bed, get the Z position according to settings MULTIPLE_PROBING and EXTRA_PROBING + * - Depending on the 'stow' flag + * - Stow the probe, or + * - Raise to the BETWEEN height + * - Return the probed Z position + * - Revert to previous tool + * + * A batch of multiple probing operations should always be preceded by use_probing_tool() invocation + * and succeeded by use_probing_tool(false), in order to avoid multiple tool changes and to end up + * with the previously active tool. + * + */ +xyz_pos_t Probe::probe_safely( + const xyz_pos_t &target, + const ProbePtRaise raise_after, // = PROBE_PT_NONE + const uint8_t move_value, // = 0 + const uint8_t verbose_level, // = 0 + const bool probe_relative, // = true + const bool sanity_check, // = true + const float z_clearance, // = Z_TWEEN_SAFE_CLEARANCE + const bool raise_after_is_rel // = false ) { - DEBUG_SECTION(log_probe, "Probe::probe_at_point", DEBUGGING(LEVELING)); + DEBUG_SECTION(log_probe, "Probe::probe_safely", DEBUGGING(LEVELING)); if (DEBUGGING(LEVELING)) { DEBUG_ECHOLNPGM( - "...(", LOGICAL_X_POSITION(rx), ", ", LOGICAL_Y_POSITION(ry), + "...(", LOGICAL_X_POSITION(target.x), ", ", LOGICAL_Y_POSITION(target.y), ", ", raise_after == PROBE_PT_RAISE ? "raise" : raise_after == PROBE_PT_LAST_STOW ? "stow (last)" : raise_after == PROBE_PT_STOW ? "stow" : "none", ", ", verbose_level, ", ", probe_relative ? "probe" : "nozzle", "_relative)" @@ -994,36 +1119,43 @@ float Probe::probe_at_point( const float safe_z = _MAX(current_position.z, z_clearance); // On delta keep Z below clip height or do_blocking_move_to will abort - xyz_pos_t npos = NUM_AXIS_ARRAY( - rx, ry, TERN(DELTA, _MIN(delta_clip_start_height, safe_z), safe_z), - current_position.i, current_position.j, current_position.k, - current_position.u, current_position.v, current_position.w - ); - if (!can_reach(npos, probe_relative)) { + xyz_pos_t npos = current_position; + npos.z = TERN(DELTA, _MIN(delta_clip_start_height, safe_z), safe_z); + if (TERN1(G38_PROBE_TARGET, move_value == 0)) { + npos.set(target.x, target.y); + } + + if (!can_reach(target, probe_relative)) { if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Not Reachable"); - return NAN; + return {NUM_AXIS_LIST(NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN,NAN)}; } if (DEBUGGING(LEVELING)) DEBUG_ECHOPGM("Move to probe"); if (probe_relative) { // Get the nozzle position, adjust for active hotend if not 0 if (DEBUGGING(LEVELING)) DEBUG_ECHOPGM("-relative"); - npos -= DIFF_TERN(HAS_HOTEND_OFFSET, offset_xy, xy_pos_t(hotend_offset[active_extruder])); + if (TERN0(G38_PROBE_TARGET, move_value > 0)) + npos -= DIFF_TERN(HAS_HOTEND_OFFSET, offset, hotend_offset[active_extruder]); + else + npos -= DIFF_TERN(HAS_HOTEND_OFFSET, offset_xy, xy_pos_t(hotend_offset[active_extruder])); } - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM(" point"); - // Move the probe to the starting XYZ - do_blocking_move_to(npos, feedRate_t(XY_PROBE_FEEDRATE_MM_S)); + if (move_value == 0) { + // Move the probe to the starting XY + do_blocking_move_to(npos, feedRate_t(XY_PROBE_FEEDRATE_MM_S)); + } // Change Z motor current to homing current TERN_(PROBING_USE_CURRENT_HOME, set_homing_current(Z_AXIS)); - float measured_z; + xyz_pos_t measured; #if ENABLED(BD_SENSOR) safe_delay(4); - measured_z = current_position.z - bdl.read(); // Difference between Z-home-relative Z and sensor reading + measured = current_position; + + measured.z = current_position.z - bdl.read(); // Difference between Z-home-relative Z and sensor reading #else // !BD_SENSOR @@ -1033,12 +1165,24 @@ float Probe::probe_at_point( // which the following deploy will handle. if (bltouch.triggered()) bltouch._reset(); #endif + + if (deploy()) { + LOOP_NUM_AXES(a) { + measured[a] = NAN; + } + } + else { - measured_z = deploy() ? NAN : run_z_probe(sanity_check, z_min_point, z_clearance) + offset.z; + if (move_value > 0) { + // Move the probe to the starting XYZ + do_blocking_move_to(npos, feedRate_t(XY_PROBE_FEEDRATE_MM_S)); + } + measured = run_probe(sanity_check, target, z_clearance, move_value) + offset; + } // Deploy succeeded and a successful measurement was done. // Raise and/or stow the probe depending on 'raise_after' and settings. - if (!isnan(measured_z)) { + if (!isnan(measured.z)) { switch (raise_after) { default: break; case PROBE_PT_RAISE: @@ -1048,13 +1192,13 @@ float Probe::probe_at_point( do_z_clearance(z_clearance); break; case PROBE_PT_STOW: case PROBE_PT_LAST_STOW: - if (stow()) measured_z = NAN; // Error on stow? + if (stow()) measured.z = NAN; // Error on stow? break; } } // If any error occurred stow the probe and set an alert - if (isnan(measured_z)) { + if (isnan(measured.z)) { // TODO: Disable steppers (unless G29_RETRY_AND_RECOVER or G29_HALT_ON_FAILURE are set). // Something definitely went wrong at this point, so it might be a good idea to release the steppers. // The user may want to quickly move the carriage or bed by hand to avoid bed damage from the (hot) nozzle. @@ -1066,10 +1210,10 @@ float Probe::probe_at_point( #endif } else { - TERN_(HAS_PTC, ptc.apply_compensation(measured_z)); - TERN_(X_AXIS_TWIST_COMPENSATION, measured_z += xatc.compensation(npos + offset_xy)); + TERN_(HAS_PTC, ptc.apply_compensation(measured.z)); + TERN_(X_AXIS_TWIST_COMPENSATION, measured.z += xatc.compensation(npos + offset_xy)); if (verbose_level > 2 || DEBUGGING(LEVELING)) - SERIAL_ECHOLNPGM("Bed X: ", LOGICAL_X_POSITION(rx), " Y: ", LOGICAL_Y_POSITION(ry), " Z: ", measured_z); + SERIAL_ECHOLNPGM("Bed X: ", LOGICAL_X_POSITION(target.x), " Y: ", LOGICAL_Y_POSITION(target.y), " Z: ", measured.z); } #endif // !BD_SENSOR @@ -1077,7 +1221,7 @@ float Probe::probe_at_point( // Restore the Z homing current TERN_(PROBING_USE_CURRENT_HOME, restore_homing_current(Z_AXIS)); - return measured_z; + return measured; } #if HAS_Z_SERVO_PROBE diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index d0f0ada34c5b..2d26ecbb94ba 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -33,7 +33,7 @@ #include "../feature/bltouch.h" #endif -#if ANY(BD_SENSOR, HAS_DELTA_SENSORLESS_PROBING) +#if ANY(BD_SENSOR, HAS_DELTA_SENSORLESS_PROBING, G38_PROBE_TARGET) #include "endstops.h" #endif @@ -96,6 +96,10 @@ class Probe { static xyz_pos_t offset; + #if ENABLED(G38_PROBE_TARGET) + static probe_target_t G38_move; + #endif + #if ANY(PREHEAT_BEFORE_PROBING, PREHEAT_BEFORE_LEVELING) static void preheat_for_probing(const celsius_t hotend_temp, const celsius_t bed_temp, const bool early=false); #endif @@ -180,6 +184,17 @@ class Probe { #endif // !IS_KINEMATIC + static xyz_pos_t probe_safely( + const xyz_pos_t &target, + const ProbePtRaise raise_after = PROBE_PT_NONE, + const uint8_t move_value = 0, + const uint8_t verbose_level = 0, + const bool probe_relative = true, + const bool sanity_check = true, + const float z_clearance = Z_TWEEN_SAFE_CLEARANCE, + const bool raise_after_is_rel = false + ); + static float probe_at_point( const float rx, const float ry, @@ -371,8 +386,8 @@ class Probe { private: #if HAS_BED_PROBE - static bool probe_down_to_z(const float z, const feedRate_t fr_mm_s); - static float run_z_probe(const bool sanity_check=true, const float z_min_point=Z_PROBE_LOW_POINT, const float z_clearance=Z_TWEEN_SAFE_CLEARANCE); + static bool probe_to_target(const xyz_pos_t &pos, const feedRate_t fr_mm_s, const uint8_t move_value); + static xyz_pos_t run_probe(const bool sanity_check, const xyz_pos_t &target, const float z_clearance, const uint8_t move_value); #endif };