/** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /** * endstops.cpp - A singleton object to manage endstops */ #include "endstops.h" #include "stepper.h" #include "../sd/cardreader.h" #include "temperature.h" #include "../lcd/marlinui.h" #if ENABLED(ENDSTOP_INTERRUPTS_FEATURE) #include HAL_PATH(../HAL, endstop_interrupts.h) #endif #if BOTH(SD_ABORT_ON_ENDSTOP_HIT, SDSUPPORT) #include "printcounter.h" // for print_job_timer #endif #if ENABLED(BLTOUCH) #include "../feature/bltouch.h" #endif #if ENABLED(JOYSTICK) #include "../feature/joystick.h" #endif #if HAS_BED_PROBE #include "probe.h" #endif Endstops endstops; // private: bool Endstops::enabled, Endstops::enabled_globally; // Initialized by settings.load() volatile Endstops::endstop_mask_t Endstops::hit_state; Endstops::endstop_mask_t Endstops::live_state = 0; #if ENDSTOP_NOISE_THRESHOLD Endstops::endstop_mask_t Endstops::validated_live_state; uint8_t Endstops::endstop_poll_count; #endif #if HAS_BED_PROBE volatile bool Endstops::z_probe_enabled = false; #endif // Initialized by settings.load() #if ENABLED(X_DUAL_ENDSTOPS) float Endstops::x2_endstop_adj; #endif #if ENABLED(Y_DUAL_ENDSTOPS) float Endstops::y2_endstop_adj; #endif #if ENABLED(Z_MULTI_ENDSTOPS) float Endstops::z2_endstop_adj; #if NUM_Z_STEPPER_DRIVERS >= 3 float Endstops::z3_endstop_adj; #if NUM_Z_STEPPER_DRIVERS >= 4 float Endstops::z4_endstop_adj; #endif #endif #endif #if ENABLED(SPI_ENDSTOPS) Endstops::tmc_spi_homing_t Endstops::tmc_spi_homing; // = 0 #endif #if ENABLED(IMPROVE_HOMING_RELIABILITY) millis_t sg_guard_period; // = 0 #endif /** * Class and Instance Methods */ void Endstops::init() { #if HAS_X_MIN #if ENABLED(ENDSTOPPULLUP_XMIN) SET_INPUT_PULLUP(X_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_XMIN) SET_INPUT_PULLDOWN(X_MIN_PIN); #else SET_INPUT(X_MIN_PIN); #endif #endif #if HAS_X2_MIN #if ENABLED(ENDSTOPPULLUP_XMIN) SET_INPUT_PULLUP(X2_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_XMIN) SET_INPUT_PULLDOWN(X2_MIN_PIN); #else SET_INPUT(X2_MIN_PIN); #endif #endif #if HAS_Y_MIN #if ENABLED(ENDSTOPPULLUP_YMIN) SET_INPUT_PULLUP(Y_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_YMIN) SET_INPUT_PULLDOWN(Y_MIN_PIN); #else SET_INPUT(Y_MIN_PIN); #endif #endif #if HAS_Y2_MIN #if ENABLED(ENDSTOPPULLUP_YMIN) SET_INPUT_PULLUP(Y2_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_YMIN) SET_INPUT_PULLDOWN(Y2_MIN_PIN); #else SET_INPUT(Y2_MIN_PIN); #endif #endif #if HAS_Z_MIN #if ENABLED(ENDSTOPPULLUP_ZMIN) SET_INPUT_PULLUP(Z_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMIN) SET_INPUT_PULLDOWN(Z_MIN_PIN); #else SET_INPUT(Z_MIN_PIN); #endif #endif #if HAS_Z2_MIN #if ENABLED(ENDSTOPPULLUP_ZMIN) SET_INPUT_PULLUP(Z2_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMIN) SET_INPUT_PULLDOWN(Z2_MIN_PIN); #else SET_INPUT(Z2_MIN_PIN); #endif #endif #if HAS_Z3_MIN #if ENABLED(ENDSTOPPULLUP_ZMIN) SET_INPUT_PULLUP(Z3_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMIN) SET_INPUT_PULLDOWN(Z3_MIN_PIN); #else SET_INPUT(Z3_MIN_PIN); #endif #endif #if HAS_Z4_MIN #if ENABLED(ENDSTOPPULLUP_ZMIN) SET_INPUT_PULLUP(Z4_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMIN) SET_INPUT_PULLDOWN(Z4_MIN_PIN); #else SET_INPUT(Z4_MIN_PIN); #endif #endif #if HAS_X_MAX #if ENABLED(ENDSTOPPULLUP_XMAX) SET_INPUT_PULLUP(X_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_XMAX) SET_INPUT_PULLDOWN(X_MAX_PIN); #else SET_INPUT(X_MAX_PIN); #endif #endif #if HAS_X2_MAX #if ENABLED(ENDSTOPPULLUP_XMAX) SET_INPUT_PULLUP(X2_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_XMAX) SET_INPUT_PULLDOWN(X2_MAX_PIN); #else SET_INPUT(X2_MAX_PIN); #endif #endif #if HAS_Y_MAX #if ENABLED(ENDSTOPPULLUP_YMAX) SET_INPUT_PULLUP(Y_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_YMAX) SET_INPUT_PULLDOWN(Y_MAX_PIN); #else SET_INPUT(Y_MAX_PIN); #endif #endif #if HAS_Y2_MAX #if ENABLED(ENDSTOPPULLUP_YMAX) SET_INPUT_PULLUP(Y2_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_YMAX) SET_INPUT_PULLDOWN(Y2_MAX_PIN); #else SET_INPUT(Y2_MAX_PIN); #endif #endif #if HAS_Z_MAX #if ENABLED(ENDSTOPPULLUP_ZMAX) SET_INPUT_PULLUP(Z_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMAX) SET_INPUT_PULLDOWN(Z_MAX_PIN); #else SET_INPUT(Z_MAX_PIN); #endif #endif #if HAS_Z2_MAX #if ENABLED(ENDSTOPPULLUP_ZMAX) SET_INPUT_PULLUP(Z2_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMAX) SET_INPUT_PULLDOWN(Z2_MAX_PIN); #else SET_INPUT(Z2_MAX_PIN); #endif #endif #if HAS_Z3_MAX #if ENABLED(ENDSTOPPULLUP_ZMAX) SET_INPUT_PULLUP(Z3_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMAX) SET_INPUT_PULLDOWN(Z3_MAX_PIN); #else SET_INPUT(Z3_MAX_PIN); #endif #endif #if HAS_Z4_MAX #if ENABLED(ENDSTOPPULLUP_ZMAX) SET_INPUT_PULLUP(Z4_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMAX) SET_INPUT_PULLDOWN(Z4_MAX_PIN); #else SET_INPUT(Z4_MAX_PIN); #endif #endif #if HAS_I_MIN #if ENABLED(ENDSTOPPULLUP_IMIN) SET_INPUT_PULLUP(I_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_IMIN) SET_INPUT_PULLDOWN(I_MIN_PIN); #else SET_INPUT(I_MIN_PIN); #endif #endif #if HAS_I_MAX #if ENABLED(ENDSTOPPULLUP_IMAX) SET_INPUT_PULLUP(I_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_IMAX) SET_INPUT_PULLDOWN(I_MAX_PIN); #else SET_INPUT(I_MAX_PIN); #endif #endif #if HAS_J_MIN #if ENABLED(ENDSTOPPULLUP_JMIN) SET_INPUT_PULLUP(J_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_IMIN) SET_INPUT_PULLDOWN(J_MIN_PIN); #else SET_INPUT(J_MIN_PIN); #endif #endif #if HAS_J_MAX #if ENABLED(ENDSTOPPULLUP_JMAX) SET_INPUT_PULLUP(J_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_JMAX) SET_INPUT_PULLDOWN(J_MAX_PIN); #else SET_INPUT(J_MAX_PIN); #endif #endif #if HAS_K_MIN #if ENABLED(ENDSTOPPULLUP_KMIN) SET_INPUT_PULLUP(K_MIN_PIN); #elif ENABLED(ENDSTOPPULLDOWN_KMIN) SET_INPUT_PULLDOWN(K_MIN_PIN); #else SET_INPUT(K_MIN_PIN); #endif #endif #if HAS_K_MAX #if ENABLED(ENDSTOPPULLUP_KMAX) SET_INPUT_PULLUP(K_MAX_PIN); #elif ENABLED(ENDSTOPPULLDOWN_KMIN) SET_INPUT_PULLDOWN(K_MAX_PIN); #else SET_INPUT(K_MAX_PIN); #endif #endif #if PIN_EXISTS(CALIBRATION) #if ENABLED(CALIBRATION_PIN_PULLUP) SET_INPUT_PULLUP(CALIBRATION_PIN); #elif ENABLED(CALIBRATION_PIN_PULLDOWN) SET_INPUT_PULLDOWN(CALIBRATION_PIN); #else SET_INPUT(CALIBRATION_PIN); #endif #endif #if USES_Z_MIN_PROBE_PIN #if ENABLED(ENDSTOPPULLUP_ZMIN_PROBE) SET_INPUT_PULLUP(Z_MIN_PROBE_PIN); #elif ENABLED(ENDSTOPPULLDOWN_ZMIN_PROBE) SET_INPUT_PULLDOWN(Z_MIN_PROBE_PIN); #else SET_INPUT(Z_MIN_PROBE_PIN); #endif #endif #if ENABLED(PROBE_ACTIVATION_SWITCH) SET_INPUT(PROBE_ACTIVATION_SWITCH_PIN); #endif TERN_(PROBE_TARE, probe.tare()); TERN_(ENDSTOP_INTERRUPTS_FEATURE, setup_endstop_interrupts()); // Enable endstops enable_globally(ENABLED(ENDSTOPS_ALWAYS_ON_DEFAULT)); } // Endstops::init // Called at ~1KHz from Temperature ISR: Poll endstop state if required void Endstops::poll() { TERN_(PINS_DEBUGGING, run_monitor()); // Report changes in endstop status #if DISABLED(ENDSTOP_INTERRUPTS_FEATURE) update(); #elif ENDSTOP_NOISE_THRESHOLD if (endstop_poll_count) update(); #endif } void Endstops::enable_globally(const bool onoff) { enabled_globally = enabled = onoff; resync(); } // Enable / disable endstop checking void Endstops::enable(const bool onoff) { enabled = onoff; resync(); } // Disable / Enable endstops based on ENSTOPS_ONLY_FOR_HOMING and global enable void Endstops::not_homing() { enabled = enabled_globally; } #if ENABLED(VALIDATE_HOMING_ENDSTOPS) // If the last move failed to trigger an endstop, call kill void Endstops::validate_homing_move() { if (trigger_state()) hit_on_purpose(); else kill(GET_TEXT_F(MSG_KILL_HOMING_FAILED)); } #endif // Enable / disable endstop z-probe checking #if HAS_BED_PROBE void Endstops::enable_z_probe(const bool onoff) { z_probe_enabled = onoff; resync(); } #endif // Get the stable endstop states when enabled void Endstops::resync() { if (!abort_enabled()) return; // If endstops/probes are disabled the loop below can hang // Wait for Temperature ISR to run at least once (runs at 1KHz) TERN(ENDSTOP_INTERRUPTS_FEATURE, update(), safe_delay(2)); while (TERN0(ENDSTOP_NOISE_THRESHOLD, endstop_poll_count)) safe_delay(1); } #if ENABLED(PINS_DEBUGGING) void Endstops::run_monitor() { if (!monitor_flag) return; static uint8_t monitor_count = 16; // offset this check from the others monitor_count += _BV(1); // 15 Hz monitor_count &= 0x7F; if (!monitor_count) monitor(); // report changes in endstop status } #endif void Endstops::event_handler() { static endstop_mask_t prev_hit_state; // = 0 if (hit_state == prev_hit_state) return; prev_hit_state = hit_state; if (hit_state) { #if HAS_STATUS_MESSAGE char LINEAR_AXIS_LIST(chrX = ' ', chrY = ' ', chrZ = ' ', chrI = ' ', chrJ = ' ', chrK = ' '), chrP = ' '; #define _SET_STOP_CHAR(A,C) (chr## A = C) #else #define _SET_STOP_CHAR(A,C) NOOP #endif #define _ENDSTOP_HIT_ECHO(A,C) do{ \ SERIAL_ECHOPGM(" " STRINGIFY(A) ":", planner.triggered_position_mm(_AXIS(A))); _SET_STOP_CHAR(A,C); }while(0) #define _ENDSTOP_HIT_TEST(A,C) \ if (TERN0(HAS_##A##_MIN, TEST(hit_state, A##_MIN)) || TERN0(HAS_##A##_MAX, TEST(hit_state, A##_MAX))) \ _ENDSTOP_HIT_ECHO(A,C) #define ENDSTOP_HIT_TEST_X() _ENDSTOP_HIT_TEST(X,'X') #define ENDSTOP_HIT_TEST_Y() _ENDSTOP_HIT_TEST(Y,'Y') #define ENDSTOP_HIT_TEST_Z() _ENDSTOP_HIT_TEST(Z,'Z') #define ENDSTOP_HIT_TEST_I() _ENDSTOP_HIT_TEST(I,'I') #define ENDSTOP_HIT_TEST_J() _ENDSTOP_HIT_TEST(J,'J') #define ENDSTOP_HIT_TEST_K() _ENDSTOP_HIT_TEST(K,'K') SERIAL_ECHO_START(); SERIAL_ECHOPGM(STR_ENDSTOPS_HIT); LINEAR_AXIS_CODE( ENDSTOP_HIT_TEST_X(), ENDSTOP_HIT_TEST_Y(), ENDSTOP_HIT_TEST_Z(), _ENDSTOP_HIT_TEST(I,'I'), _ENDSTOP_HIT_TEST(J,'J'), _ENDSTOP_HIT_TEST(K,'K') ); #if USES_Z_MIN_PROBE_PIN #define P_AXIS Z_AXIS if (TEST(hit_state, Z_MIN_PROBE)) _ENDSTOP_HIT_ECHO(P, 'P'); #endif SERIAL_EOL(); TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT GANG_N_1(LINEAR_AXES, " %c") " %c"), GET_TEXT(MSG_LCD_ENDSTOPS), LINEAR_AXIS_LIST(chrX, chrY, chrZ, chrI, chrJ, chrK), chrP ) ); #if BOTH(SD_ABORT_ON_ENDSTOP_HIT, SDSUPPORT) if (planner.abort_on_endstop_hit) { card.abortFilePrintNow(); quickstop_stepper(); thermalManager.disable_all_heaters(); print_job_timer.stop(); } #endif } } #pragma GCC diagnostic push #if GCC_VERSION <= 50000 #pragma GCC diagnostic ignored "-Wunused-function" #endif static void print_es_state(const bool is_hit, FSTR_P const flabel=nullptr) { if (flabel) SERIAL_ECHOF(flabel); SERIAL_ECHOPGM(": "); SERIAL_ECHOLNF(is_hit ? F(STR_ENDSTOP_HIT) : F(STR_ENDSTOP_OPEN)); } #pragma GCC diagnostic pop void _O2 Endstops::report_states() { TERN_(BLTOUCH, bltouch._set_SW_mode()); SERIAL_ECHOLNPGM(STR_M119_REPORT); #define ES_REPORT(S) print_es_state(READ(S##_PIN) != S##_ENDSTOP_INVERTING, F(STR_##S)) #if HAS_X_MIN ES_REPORT(X_MIN); #endif #if HAS_X2_MIN ES_REPORT(X2_MIN); #endif #if HAS_X_MAX ES_REPORT(X_MAX); #endif #if HAS_X2_MAX ES_REPORT(X2_MAX); #endif #if HAS_Y_MIN ES_REPORT(Y_MIN); #endif #if HAS_Y2_MIN ES_REPORT(Y2_MIN); #endif #if HAS_Y_MAX ES_REPORT(Y_MAX); #endif #if HAS_Y2_MAX ES_REPORT(Y2_MAX); #endif #if HAS_Z_MIN ES_REPORT(Z_MIN); #endif #if HAS_Z2_MIN ES_REPORT(Z2_MIN); #endif #if HAS_Z3_MIN ES_REPORT(Z3_MIN); #endif #if HAS_Z4_MIN ES_REPORT(Z4_MIN); #endif #if HAS_Z_MAX ES_REPORT(Z_MAX); #endif #if HAS_Z2_MAX ES_REPORT(Z2_MAX); #endif #if HAS_Z3_MAX ES_REPORT(Z3_MAX); #endif #if HAS_Z4_MAX ES_REPORT(Z4_MAX); #endif #if HAS_I_MIN ES_REPORT(I_MIN); #endif #if HAS_I_MAX ES_REPORT(I_MAX); #endif #if HAS_J_MIN ES_REPORT(J_MIN); #endif #if HAS_J_MAX ES_REPORT(J_MAX); #endif #if HAS_K_MIN ES_REPORT(K_MIN); #endif #if HAS_K_MAX ES_REPORT(K_MAX); #endif #if BOTH(PROBE_ACTIVATION_SWITCH) print_es_state(probe_switch_activated(), F(STR_PROBE_EN)); #endif #if USES_Z_MIN_PROBE_PIN print_es_state(PROBE_TRIGGERED(), F(STR_Z_PROBE)); #endif #if MULTI_FILAMENT_SENSOR #define _CASE_RUNOUT(N) case N: pin = FIL_RUNOUT##N##_PIN; state = FIL_RUNOUT##N##_STATE; break; LOOP_S_LE_N(i, 1, NUM_RUNOUT_SENSORS) { pin_t pin; uint8_t state; switch (i) { default: continue; REPEAT_1(NUM_RUNOUT_SENSORS, _CASE_RUNOUT) } SERIAL_ECHOPGM(STR_FILAMENT); if (i > 1) SERIAL_CHAR(' ', '0' + i); print_es_state(extDigitalRead(pin) != state); } #undef _CASE_RUNOUT #elif HAS_FILAMENT_SENSOR print_es_state(READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE, F(STR_FILAMENT)); #endif TERN_(BLTOUCH, bltouch._reset_SW_mode()); TERN_(JOYSTICK_DEBUG, joystick.report()); } // Endstops::report_states // The following routines are called from an ISR context. It could be the temperature ISR, the // endstop ISR or the Stepper ISR. #if HAS_DELTA_SENSORLESS_PROBING #define __ENDSTOP(AXIS, ...) AXIS ##_MAX #define _ENDSTOP_PIN(AXIS, ...) AXIS ##_MAX_PIN #define _ENDSTOP_INVERTING(AXIS, ...) AXIS ##_MAX_ENDSTOP_INVERTING #else #define __ENDSTOP(AXIS, MINMAX) AXIS ##_## MINMAX #define _ENDSTOP_PIN(AXIS, MINMAX) AXIS ##_## MINMAX ##_PIN #define _ENDSTOP_INVERTING(AXIS, MINMAX) AXIS ##_## MINMAX ##_ENDSTOP_INVERTING #endif #define _ENDSTOP(AXIS, MINMAX) __ENDSTOP(AXIS, MINMAX) // Check endstops - Could be called from Temperature ISR! void Endstops::update() { #if !ENDSTOP_NOISE_THRESHOLD if (!abort_enabled()) return; #endif #define UPDATE_ENDSTOP_BIT(AXIS, MINMAX) SET_BIT_TO(live_state, _ENDSTOP(AXIS, MINMAX), (READ(_ENDSTOP_PIN(AXIS, MINMAX)) != _ENDSTOP_INVERTING(AXIS, MINMAX))) #define COPY_LIVE_STATE(SRC_BIT, DST_BIT) SET_BIT_TO(live_state, DST_BIT, TEST(live_state, SRC_BIT)) #if ENABLED(G38_PROBE_TARGET) && NONE(CORE_IS_XY, CORE_IS_XZ, MARKFORGED_XY) #define HAS_G38_PROBE 1 // For G38 moves check the probe's pin for ALL movement if (G38_move) UPDATE_ENDSTOP_BIT(Z, TERN(USES_Z_MIN_PROBE_PIN, MIN_PROBE, MIN)); #endif // With Dual X, endstops are only checked in the homing direction for the active extruder #define X_MIN_TEST() TERN1(DUAL_X_CARRIAGE, TERN0(X_HOME_TO_MIN, stepper.last_moved_extruder == 0) || TERN0(X2_HOME_TO_MIN, stepper.last_moved_extruder != 0)) #define X_MAX_TEST() TERN1(DUAL_X_CARRIAGE, TERN0(X_HOME_TO_MAX, stepper.last_moved_extruder == 0) || TERN0(X2_HOME_TO_MAX, stepper.last_moved_extruder != 0)) // Use HEAD for core axes, AXIS for others #if ANY(CORE_IS_XY, CORE_IS_XZ, MARKFORGED_XY) #define X_AXIS_HEAD X_HEAD #else #define X_AXIS_HEAD X_AXIS #endif #if ANY(CORE_IS_XY, CORE_IS_YZ, MARKFORGED_XY) #define Y_AXIS_HEAD Y_HEAD #else #define Y_AXIS_HEAD Y_AXIS #endif #if CORE_IS_XZ || CORE_IS_YZ #define Z_AXIS_HEAD Z_HEAD #else #define Z_AXIS_HEAD Z_AXIS #endif #define I_AXIS_HEAD I_AXIS #define J_AXIS_HEAD J_AXIS #define K_AXIS_HEAD K_AXIS /** * Check and update endstops */ #if HAS_X_MIN && !X_SPI_SENSORLESS UPDATE_ENDSTOP_BIT(X, MIN); #if ENABLED(X_DUAL_ENDSTOPS) #if HAS_X2_MIN UPDATE_ENDSTOP_BIT(X2, MIN); #else COPY_LIVE_STATE(X_MIN, X2_MIN); #endif #endif #endif #if HAS_X_MAX && !X_SPI_SENSORLESS UPDATE_ENDSTOP_BIT(X, MAX); #if ENABLED(X_DUAL_ENDSTOPS) #if HAS_X2_MAX UPDATE_ENDSTOP_BIT(X2, MAX); #else COPY_LIVE_STATE(X_MAX, X2_MAX); #endif #endif #endif #if HAS_Y_MIN && !Y_SPI_SENSORLESS UPDATE_ENDSTOP_BIT(Y, MIN); #if ENABLED(Y_DUAL_ENDSTOPS) #if HAS_Y2_MIN UPDATE_ENDSTOP_BIT(Y2, MIN); #else COPY_LIVE_STATE(Y_MIN, Y2_MIN); #endif #endif #endif #if HAS_Y_MAX && !Y_SPI_SENSORLESS UPDATE_ENDSTOP_BIT(Y, MAX); #if ENABLED(Y_DUAL_ENDSTOPS) #if HAS_Y2_MAX UPDATE_ENDSTOP_BIT(Y2, MAX); #else COPY_LIVE_STATE(Y_MAX, Y2_MAX); #endif #endif #endif #if HAS_Z_MIN && NONE(Z_SPI_SENSORLESS, Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) UPDATE_ENDSTOP_BIT(Z, MIN); #if ENABLED(Z_MULTI_ENDSTOPS) #if HAS_Z2_MIN UPDATE_ENDSTOP_BIT(Z2, MIN); #else COPY_LIVE_STATE(Z_MIN, Z2_MIN); #endif #if NUM_Z_STEPPER_DRIVERS >= 3 #if HAS_Z3_MIN UPDATE_ENDSTOP_BIT(Z3, MIN); #else COPY_LIVE_STATE(Z_MIN, Z3_MIN); #endif #endif #if NUM_Z_STEPPER_DRIVERS >= 4 #if HAS_Z4_MIN UPDATE_ENDSTOP_BIT(Z4, MIN); #else COPY_LIVE_STATE(Z_MIN, Z4_MIN); #endif #endif #endif #endif #if HAS_BED_PROBE // When closing the gap check the enabled probe if (probe_switch_activated()) UPDATE_ENDSTOP_BIT(Z, TERN(USES_Z_MIN_PROBE_PIN, MIN_PROBE, MIN)); #endif #if HAS_Z_MAX && !Z_SPI_SENSORLESS // Check both Z dual endstops #if ENABLED(Z_MULTI_ENDSTOPS) UPDATE_ENDSTOP_BIT(Z, MAX); #if HAS_Z2_MAX UPDATE_ENDSTOP_BIT(Z2, MAX); #else COPY_LIVE_STATE(Z_MAX, Z2_MAX); #endif #if NUM_Z_STEPPER_DRIVERS >= 3 #if HAS_Z3_MAX UPDATE_ENDSTOP_BIT(Z3, MAX); #else COPY_LIVE_STATE(Z_MAX, Z3_MAX); #endif #endif #if NUM_Z_STEPPER_DRIVERS >= 4 #if HAS_Z4_MAX UPDATE_ENDSTOP_BIT(Z4, MAX); #else COPY_LIVE_STATE(Z_MAX, Z4_MAX); #endif #endif #elif TERN1(USES_Z_MIN_PROBE_PIN, Z_MAX_PIN != Z_MIN_PROBE_PIN) // If this pin isn't the bed probe it's the Z endstop UPDATE_ENDSTOP_BIT(Z, MAX); #endif #endif #if HAS_I_MIN && !I_SPI_SENSORLESS #if ENABLED(I_DUAL_ENDSTOPS) UPDATE_ENDSTOP_BIT(I, MIN); #if HAS_I2_MIN UPDATE_ENDSTOP_BIT(I2, MAX); #else COPY_LIVE_STATE(I_MIN, I2_MIN); #endif #else UPDATE_ENDSTOP_BIT(I, MIN); #endif #endif #if HAS_I_MAX && !I_SPI_SENSORLESS #if ENABLED(I_DUAL_ENDSTOPS) UPDATE_ENDSTOP_BIT(I, MAX); #if HAS_I2_MAX UPDATE_ENDSTOP_BIT(I2, MAX); #else COPY_LIVE_STATE(I_MAX, I2_MAX); #endif #else UPDATE_ENDSTOP_BIT(I, MAX); #endif #endif #if HAS_J_MIN && !J_SPI_SENSORLESS #if ENABLED(J_DUAL_ENDSTOPS) UPDATE_ENDSTOP_BIT(J, MIN); #if HAS_J2_MIN UPDATE_ENDSTOP_BIT(J2, MIN); #else COPY_LIVE_STATE(J_MIN, J2_MIN); #endif #else UPDATE_ENDSTOP_BIT(J, MIN); #endif #endif #if HAS_J_MAX && !J_SPI_SENSORLESS #if ENABLED(J_DUAL_ENDSTOPS) UPDATE_ENDSTOP_BIT(J, MAX); #if HAS_J2_MAX UPDATE_ENDSTOP_BIT(J2, MAX); #else COPY_LIVE_STATE(J_MAX, J2_MAX); #endif #else UPDATE_ENDSTOP_BIT(J, MAX); #endif #endif #if HAS_K_MIN && !K_SPI_SENSORLESS #if ENABLED(K_DUAL_ENDSTOPS) UPDATE_ENDSTOP_BIT(K, MIN); #if HAS_K2_MIN UPDATE_ENDSTOP_BIT(K2, MIN); #else COPY_LIVE_STATE(K_MIN, K2_MIN); #endif #else UPDATE_ENDSTOP_BIT(K, MIN); #endif #endif #if HAS_K_MAX && !K_SPI_SENSORLESS #if ENABLED(K_DUAL_ENDSTOPS) UPDATE_ENDSTOP_BIT(K, MAX); #if HAS_K2_MAX UPDATE_ENDSTOP_BIT(K2, MAX); #else COPY_LIVE_STATE(K_MAX, K2_MAX); #endif #else UPDATE_ENDSTOP_BIT(K, MAX); #endif #endif #if ENDSTOP_NOISE_THRESHOLD /** * Filtering out noise on endstops requires a delayed decision. Let's assume, due to noise, * that 50% of endstop signal samples are good and 50% are bad (assuming normal distribution * of random noise). Then the first sample has a 50% chance to be good or bad. The 2nd sample * also has a 50% chance to be good or bad. The chances of 2 samples both being bad becomes * 50% of 50%, or 25%. That was the previous implementation of Marlin endstop handling. It * reduces chances of bad readings in half, at the cost of 1 extra sample period, but chances * still exist. The only way to reduce them further is to increase the number of samples. * To reduce the chance to 1% (1/128th) requires 7 samples (adding 7ms of delay). */ static endstop_mask_t old_live_state; if (old_live_state != live_state) { endstop_poll_count = ENDSTOP_NOISE_THRESHOLD; old_live_state = live_state; } else if (endstop_poll_count && !--endstop_poll_count) validated_live_state = live_state; if (!abort_enabled()) return; #endif // Test the current status of an endstop #define TEST_ENDSTOP(ENDSTOP) (TEST(state(), ENDSTOP)) // Record endstop was hit #define _ENDSTOP_HIT(AXIS, MINMAX) SBI(hit_state, _ENDSTOP(AXIS, MINMAX)) // Call the endstop triggered routine for single endstops #define PROCESS_ENDSTOP(AXIS, MINMAX) do { \ if (TEST_ENDSTOP(_ENDSTOP(AXIS, MINMAX))) { \ _ENDSTOP_HIT(AXIS, MINMAX); \ planner.endstop_triggered(_AXIS(AXIS)); \ } \ }while(0) // Core Sensorless Homing needs to test an Extra Pin #define CORE_DIAG(QQ,A,MM) (CORE_IS_##QQ && A##_SENSORLESS && !A##_SPI_SENSORLESS && HAS_##A##_##MM) #define PROCESS_CORE_ENDSTOP(A1,M1,A2,M2) do { \ if (TEST_ENDSTOP(_ENDSTOP(A1,M1))) { \ _ENDSTOP_HIT(A2,M2); \ planner.endstop_triggered(_AXIS(A2)); \ } \ }while(0) // Call the endstop triggered routine for dual endstops #define PROCESS_DUAL_ENDSTOP(A, MINMAX) do { \ const byte dual_hit = TEST_ENDSTOP(_ENDSTOP(A, MINMAX)) | (TEST_ENDSTOP(_ENDSTOP(A##2, MINMAX)) << 1); \ if (dual_hit) { \ _ENDSTOP_HIT(A, MINMAX); \ /* if not performing home or if both endstops were triggered during homing... */ \ if (!stepper.separate_multi_axis || dual_hit == 0b11) \ planner.endstop_triggered(_AXIS(A)); \ } \ }while(0) #define PROCESS_TRIPLE_ENDSTOP(A, MINMAX) do { \ const byte triple_hit = TEST_ENDSTOP(_ENDSTOP(A, MINMAX)) | (TEST_ENDSTOP(_ENDSTOP(A##2, MINMAX)) << 1) | (TEST_ENDSTOP(_ENDSTOP(A##3, MINMAX)) << 2); \ if (triple_hit) { \ _ENDSTOP_HIT(A, MINMAX); \ /* if not performing home or if both endstops were triggered during homing... */ \ if (!stepper.separate_multi_axis || triple_hit == 0b111) \ planner.endstop_triggered(_AXIS(A)); \ } \ }while(0) #define PROCESS_QUAD_ENDSTOP(A, MINMAX) do { \ const byte quad_hit = TEST_ENDSTOP(_ENDSTOP(A, MINMAX)) | (TEST_ENDSTOP(_ENDSTOP(A##2, MINMAX)) << 1) | (TEST_ENDSTOP(_ENDSTOP(A##3, MINMAX)) << 2) | (TEST_ENDSTOP(_ENDSTOP(A##4, MINMAX)) << 3); \ if (quad_hit) { \ _ENDSTOP_HIT(A, MINMAX); \ /* if not performing home or if both endstops were triggered during homing... */ \ if (!stepper.separate_multi_axis || quad_hit == 0b1111) \ planner.endstop_triggered(_AXIS(A)); \ } \ }while(0) #if ENABLED(X_DUAL_ENDSTOPS) #define PROCESS_ENDSTOP_X(MINMAX) PROCESS_DUAL_ENDSTOP(X, MINMAX) #else #define PROCESS_ENDSTOP_X(MINMAX) if (X_##MINMAX##_TEST()) PROCESS_ENDSTOP(X, MINMAX) #endif #if ENABLED(Y_DUAL_ENDSTOPS) #define PROCESS_ENDSTOP_Y(MINMAX) PROCESS_DUAL_ENDSTOP(Y, MINMAX) #else #define PROCESS_ENDSTOP_Y(MINMAX) PROCESS_ENDSTOP(Y, MINMAX) #endif #if DISABLED(Z_MULTI_ENDSTOPS) #define PROCESS_ENDSTOP_Z(MINMAX) PROCESS_ENDSTOP(Z, MINMAX) #elif NUM_Z_STEPPER_DRIVERS == 4 #define PROCESS_ENDSTOP_Z(MINMAX) PROCESS_QUAD_ENDSTOP(Z, MINMAX) #elif NUM_Z_STEPPER_DRIVERS == 3 #define PROCESS_ENDSTOP_Z(MINMAX) PROCESS_TRIPLE_ENDSTOP(Z, MINMAX) #else #define PROCESS_ENDSTOP_Z(MINMAX) PROCESS_DUAL_ENDSTOP(Z, MINMAX) #endif #if HAS_G38_PROBE #define _G38_OPEN_STATE TERN(G38_PROBE_AWAY, (G38_move >= 4), LOW) // For G38 moves check the probe's pin for ALL movement if (G38_move && TEST_ENDSTOP(_ENDSTOP(Z, TERN(USES_Z_MIN_PROBE_PIN, MIN_PROBE, MIN))) != _G38_OPEN_STATE) { if (stepper.axis_is_moving(X_AXIS)) { _ENDSTOP_HIT(X, TERN(X_HOME_TO_MIN, MIN, MAX)); planner.endstop_triggered(X_AXIS); } #if HAS_Y_AXIS else if (stepper.axis_is_moving(Y_AXIS)) { _ENDSTOP_HIT(Y, TERN(Y_HOME_TO_MIN, MIN, MAX)); planner.endstop_triggered(Y_AXIS); } #endif #if HAS_Z_AXIS else if (stepper.axis_is_moving(Z_AXIS)) { _ENDSTOP_HIT(Z, TERN(Z_HOME_TO_MIN, MIN, MAX)); planner.endstop_triggered(Z_AXIS); } #endif G38_did_trigger = true; } #endif // Signal, after validation, if an endstop limit is pressed or not if (stepper.axis_is_moving(X_AXIS)) { if (stepper.motor_direction(X_AXIS_HEAD)) { // -direction #if HAS_X_MIN || (X_SPI_SENSORLESS && X_HOME_TO_MIN) PROCESS_ENDSTOP_X(MIN); #if CORE_DIAG(XY, Y, MIN) PROCESS_CORE_ENDSTOP(Y,MIN,X,MIN); #elif CORE_DIAG(XY, Y, MAX) PROCESS_CORE_ENDSTOP(Y,MAX,X,MIN); #elif CORE_DIAG(XZ, Z, MIN) PROCESS_CORE_ENDSTOP(Z,MIN,X,MIN); #elif CORE_DIAG(XZ, Z, MAX) PROCESS_CORE_ENDSTOP(Z,MAX,X,MIN); #endif #endif } else { // +direction #if HAS_X_MAX || (X_SPI_SENSORLESS && X_HOME_TO_MAX) PROCESS_ENDSTOP_X(MAX); #if CORE_DIAG(XY, Y, MIN) PROCESS_CORE_ENDSTOP(Y,MIN,X,MAX); #elif CORE_DIAG(XY, Y, MAX) PROCESS_CORE_ENDSTOP(Y,MAX,X,MAX); #elif CORE_DIAG(XZ, Z, MIN) PROCESS_CORE_ENDSTOP(Z,MIN,X,MAX); #elif CORE_DIAG(XZ, Z, MAX) PROCESS_CORE_ENDSTOP(Z,MAX,X,MAX); #endif #endif } } #if HAS_Y_AXIS if (stepper.axis_is_moving(Y_AXIS)) { if (stepper.motor_direction(Y_AXIS_HEAD)) { // -direction #if HAS_Y_MIN || (Y_SPI_SENSORLESS && Y_HOME_TO_MIN) PROCESS_ENDSTOP_Y(MIN); #if CORE_DIAG(XY, X, MIN) PROCESS_CORE_ENDSTOP(X,MIN,Y,MIN); #elif CORE_DIAG(XY, X, MAX) PROCESS_CORE_ENDSTOP(X,MAX,Y,MIN); #elif CORE_DIAG(YZ, Z, MIN) PROCESS_CORE_ENDSTOP(Z,MIN,Y,MIN); #elif CORE_DIAG(YZ, Z, MAX) PROCESS_CORE_ENDSTOP(Z,MAX,Y,MIN); #endif #endif } else { // +direction #if HAS_Y_MAX || (Y_SPI_SENSORLESS && Y_HOME_TO_MAX) PROCESS_ENDSTOP_Y(MAX); #if CORE_DIAG(XY, X, MIN) PROCESS_CORE_ENDSTOP(X,MIN,Y,MAX); #elif CORE_DIAG(XY, X, MAX) PROCESS_CORE_ENDSTOP(X,MAX,Y,MAX); #elif CORE_DIAG(YZ, Z, MIN) PROCESS_CORE_ENDSTOP(Z,MIN,Y,MAX); #elif CORE_DIAG(YZ, Z, MAX) PROCESS_CORE_ENDSTOP(Z,MAX,Y,MAX); #endif #endif } } #endif #if HAS_Z_AXIS if (stepper.axis_is_moving(Z_AXIS)) { if (stepper.motor_direction(Z_AXIS_HEAD)) { // Z -direction. Gantry down, bed up. #if HAS_Z_MIN || (Z_SPI_SENSORLESS && Z_HOME_TO_MIN) if ( TERN1(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN, z_probe_enabled) && TERN1(USES_Z_MIN_PROBE_PIN, !z_probe_enabled) ) PROCESS_ENDSTOP_Z(MIN); #if CORE_DIAG(XZ, X, MIN) PROCESS_CORE_ENDSTOP(X,MIN,Z,MIN); #elif CORE_DIAG(XZ, X, MAX) PROCESS_CORE_ENDSTOP(X,MAX,Z,MIN); #elif CORE_DIAG(YZ, Y, MIN) PROCESS_CORE_ENDSTOP(Y,MIN,Z,MIN); #elif CORE_DIAG(YZ, Y, MAX) PROCESS_CORE_ENDSTOP(Y,MAX,Z,MIN); #endif #endif // When closing the gap check the enabled probe #if USES_Z_MIN_PROBE_PIN if (z_probe_enabled) PROCESS_ENDSTOP(Z, MIN_PROBE); #endif } else { // Z +direction. Gantry up, bed down. #if HAS_Z_MAX || (Z_SPI_SENSORLESS && Z_HOME_TO_MAX) #if ENABLED(Z_MULTI_ENDSTOPS) PROCESS_ENDSTOP_Z(MAX); #elif TERN1(USES_Z_MIN_PROBE_PIN, Z_MAX_PIN != Z_MIN_PROBE_PIN) // No probe or probe is Z_MIN || Probe is not Z_MAX PROCESS_ENDSTOP(Z, MAX); #endif #if CORE_DIAG(XZ, X, MIN) PROCESS_CORE_ENDSTOP(X,MIN,Z,MAX); #elif CORE_DIAG(XZ, X, MAX) PROCESS_CORE_ENDSTOP(X,MAX,Z,MAX); #elif CORE_DIAG(YZ, Y, MIN) PROCESS_CORE_ENDSTOP(Y,MIN,Z,MAX); #elif CORE_DIAG(YZ, Y, MAX) PROCESS_CORE_ENDSTOP(Y,MAX,Z,MAX); #endif #endif } } #endif #if LINEAR_AXES >= 4 if (stepper.axis_is_moving(I_AXIS)) { if (stepper.motor_direction(I_AXIS_HEAD)) { // -direction #if HAS_I_MIN || (I_SPI_SENSORLESS && I_HOME_TO_MIN) PROCESS_ENDSTOP(I, MIN); #endif } else { // +direction #if HAS_I_MAX || (I_SPI_SENSORLESS && I_HOME_TO_MAX) PROCESS_ENDSTOP(I, MAX); #endif } } #endif #if LINEAR_AXES >= 5 if (stepper.axis_is_moving(J_AXIS)) { if (stepper.motor_direction(J_AXIS_HEAD)) { // -direction #if HAS_J_MIN || (J_SPI_SENSORLESS && J_HOME_TO_MIN) PROCESS_ENDSTOP(J, MIN); #endif } else { // +direction #if HAS_J_MAX || (J_SPI_SENSORLESS && J_HOME_TO_MAX) PROCESS_ENDSTOP(J, MAX); #endif } } #endif #if LINEAR_AXES >= 6 if (stepper.axis_is_moving(K_AXIS)) { if (stepper.motor_direction(K_AXIS_HEAD)) { // -direction #if HAS_K_MIN || (K_SPI_SENSORLESS && K_HOME_TO_MIN) PROCESS_ENDSTOP(K, MIN); #endif } else { // +direction #if HAS_K_MAX || (K_SPI_SENSORLESS && K_HOME_TO_MAX) PROCESS_ENDSTOP(K, MAX); #endif } } #endif } // Endstops::update() #if ENABLED(SPI_ENDSTOPS) bool Endstops::tmc_spi_homing_check() { bool hit = false; #if X_SPI_SENSORLESS if (tmc_spi_homing.x && (stepperX.test_stall_status() #if ANY(CORE_IS_XY, MARKFORGED_XY) && Y_SPI_SENSORLESS || stepperY.test_stall_status() #elif CORE_IS_XZ && Z_SPI_SENSORLESS || stepperZ.test_stall_status() #endif )) { SBI(live_state, X_ENDSTOP); hit = true; } #endif #if Y_SPI_SENSORLESS if (tmc_spi_homing.y && (stepperY.test_stall_status() #if ANY(CORE_IS_XY, MARKFORGED_XY) && X_SPI_SENSORLESS || stepperX.test_stall_status() #elif CORE_IS_YZ && Z_SPI_SENSORLESS || stepperZ.test_stall_status() #endif )) { SBI(live_state, Y_ENDSTOP); hit = true; } #endif #if Z_SPI_SENSORLESS if (tmc_spi_homing.z && (stepperZ.test_stall_status() #if CORE_IS_XZ && X_SPI_SENSORLESS || stepperX.test_stall_status() #elif CORE_IS_YZ && Y_SPI_SENSORLESS || stepperY.test_stall_status() #endif )) { SBI(live_state, Z_ENDSTOP); hit = true; } #endif #if I_SPI_SENSORLESS if (tmc_spi_homing.i && stepperI.test_stall_status()) { SBI(live_state, I_ENDSTOP); hit = true; } #endif #if J_SPI_SENSORLESS if (tmc_spi_homing.j && stepperJ.test_stall_status()) { SBI(live_state, J_ENDSTOP); hit = true; } #endif #if K_SPI_SENSORLESS if (tmc_spi_homing.k && stepperK.test_stall_status()) { SBI(live_state, K_ENDSTOP); hit = true; } #endif if (TERN0(ENDSTOP_INTERRUPTS_FEATURE, hit)) update(); return hit; } void Endstops::clear_endstop_state() { TERN_(X_SPI_SENSORLESS, CBI(live_state, X_ENDSTOP)); TERN_(Y_SPI_SENSORLESS, CBI(live_state, Y_ENDSTOP)); TERN_(Z_SPI_SENSORLESS, CBI(live_state, Z_ENDSTOP)); TERN_(I_SPI_SENSORLESS, CBI(live_state, I_ENDSTOP)); TERN_(J_SPI_SENSORLESS, CBI(live_state, J_ENDSTOP)); TERN_(K_SPI_SENSORLESS, CBI(live_state, K_ENDSTOP)); } #endif // SPI_ENDSTOPS #if ENABLED(PINS_DEBUGGING) bool Endstops::monitor_flag = false; /** * Monitor Endstops and Z Probe for changes * * If a change is detected then the LED is toggled and * a message is sent out the serial port. * * Yes, we could miss a rapid back & forth change but * that won't matter because this is all manual. */ void Endstops::monitor() { static uint16_t old_live_state_local = 0; static uint8_t local_LED_status = 0; uint16_t live_state_local = 0; #define ES_GET_STATE(S) if (READ(S##_PIN)) SBI(live_state_local, S) #if HAS_X_MIN ES_GET_STATE(X_MIN); #endif #if HAS_X_MAX ES_GET_STATE(X_MAX); #endif #if HAS_Y_MIN ES_GET_STATE(Y_MIN); #endif #if HAS_Y_MAX ES_GET_STATE(Y_MAX); #endif #if HAS_Z_MIN ES_GET_STATE(Z_MIN); #endif #if HAS_Z_MAX ES_GET_STATE(Z_MAX); #endif #if HAS_Z_MIN_PROBE_PIN ES_GET_STATE(Z_MIN_PROBE); #endif #if HAS_X2_MIN ES_GET_STATE(X2_MIN); #endif #if HAS_X2_MAX ES_GET_STATE(X2_MAX); #endif #if HAS_Y2_MIN ES_GET_STATE(Y2_MIN); #endif #if HAS_Y2_MAX ES_GET_STATE(Y2_MAX); #endif #if HAS_Z2_MIN ES_GET_STATE(Z2_MIN); #endif #if HAS_Z2_MAX ES_GET_STATE(Z2_MAX); #endif #if HAS_Z3_MIN ES_GET_STATE(Z3_MIN); #endif #if HAS_Z3_MAX ES_GET_STATE(Z3_MAX); #endif #if HAS_Z4_MIN ES_GET_STATE(Z4_MIN); #endif #if HAS_Z4_MAX ES_GET_STATE(Z4_MAX); #endif #if HAS_I_MAX ES_GET_STATE(I_MAX); #endif #if HAS_I_MIN ES_GET_STATE(I_MIN); #endif #if HAS_J_MAX ES_GET_STATE(J_MAX); #endif #if HAS_J_MIN ES_GET_STATE(J_MIN); #endif #if HAS_K_MAX ES_GET_STATE(K_MAX); #endif #if HAS_K_MIN ES_GET_STATE(K_MIN); #endif uint16_t endstop_change = live_state_local ^ old_live_state_local; #define ES_REPORT_CHANGE(S) if (TEST(endstop_change, S)) SERIAL_ECHOPGM(" " STRINGIFY(S) ":", TEST(live_state_local, S)) if (endstop_change) { #if HAS_X_MIN ES_REPORT_CHANGE(X_MIN); #endif #if HAS_X_MAX ES_REPORT_CHANGE(X_MAX); #endif #if HAS_Y_MIN ES_REPORT_CHANGE(Y_MIN); #endif #if HAS_Y_MAX ES_REPORT_CHANGE(Y_MAX); #endif #if HAS_Z_MIN ES_REPORT_CHANGE(Z_MIN); #endif #if HAS_Z_MAX ES_REPORT_CHANGE(Z_MAX); #endif #if HAS_Z_MIN_PROBE_PIN ES_REPORT_CHANGE(Z_MIN_PROBE); #endif #if HAS_X2_MIN ES_REPORT_CHANGE(X2_MIN); #endif #if HAS_X2_MAX ES_REPORT_CHANGE(X2_MAX); #endif #if HAS_Y2_MIN ES_REPORT_CHANGE(Y2_MIN); #endif #if HAS_Y2_MAX ES_REPORT_CHANGE(Y2_MAX); #endif #if HAS_Z2_MIN ES_REPORT_CHANGE(Z2_MIN); #endif #if HAS_Z2_MAX ES_REPORT_CHANGE(Z2_MAX); #endif #if HAS_Z3_MIN ES_REPORT_CHANGE(Z3_MIN); #endif #if HAS_Z3_MAX ES_REPORT_CHANGE(Z3_MAX); #endif #if HAS_Z4_MIN ES_REPORT_CHANGE(Z4_MIN); #endif #if HAS_Z4_MAX ES_REPORT_CHANGE(Z4_MAX); #endif #if HAS_I_MIN ES_REPORT_CHANGE(I_MIN); #endif #if HAS_I_MAX ES_REPORT_CHANGE(I_MAX); #endif #if HAS_J_MIN ES_REPORT_CHANGE(J_MIN); #endif #if HAS_J_MAX ES_REPORT_CHANGE(J_MAX); #endif #if HAS_K_MIN ES_REPORT_CHANGE(K_MIN); #endif #if HAS_K_MAX ES_REPORT_CHANGE(K_MAX); #endif SERIAL_ECHOLNPGM("\n"); set_pwm_duty(pin_t(LED_PIN), local_LED_status); local_LED_status ^= 255; old_live_state_local = live_state_local; } } #endif // PINS_DEBUGGING