diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index ab0b7cdf20..1202f1f517 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -3544,6 +3544,16 @@ #define LASER_TEST_PULSE_MIN 1 // Used with Laser Control Menu #define LASER_TEST_PULSE_MAX 999 // Caution: Menu may not show more than 3 characters + /** + * Laser Safety Timeout + * + * The laser should be turned off when there is no movement for a period of time. + * Consider material flammability, cut rate, and G-code order when setting this + * value. Too low and it could turn off during a very slow move; too high and + * the material could ignite. + */ + #define LASER_SAFETY_TIMEOUT_MS 1000 // (ms) + /** * Enable inline laser power to be handled in the planner / stepper routines. * Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I) diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index e25c3a4911..76bc988483 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -423,34 +423,35 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { kill(); } + const bool has_blocks = planner.has_blocks_queued(); // Any moves in the planner? + if (has_blocks) gcode.reset_stepper_timeout(ms); // Reset timeout for M18/M84, M85 max 'kill', and laser. + // M18 / M84 : Handle steppers inactive time timeout - if (gcode.stepper_inactive_time) { + #if HAS_DISABLE_INACTIVE_AXIS + if (gcode.stepper_inactive_time) { - static bool already_shutdown_steppers; // = false + static bool already_shutdown_steppers; // = false - // Any moves in the planner? Resets both the M18/M84 - // activity timeout and the M85 max 'kill' timeout - if (planner.has_blocks_queued()) - gcode.reset_stepper_timeout(ms); - else if (!do_reset_timeout && gcode.stepper_inactive_timeout()) { - if (!already_shutdown_steppers) { - already_shutdown_steppers = true; // L6470 SPI will consume 99% of free time without this - - // Individual axes will be disabled if configured - TERN_(DISABLE_INACTIVE_X, stepper.disable_axis(X_AXIS)); - TERN_(DISABLE_INACTIVE_Y, stepper.disable_axis(Y_AXIS)); - TERN_(DISABLE_INACTIVE_Z, stepper.disable_axis(Z_AXIS)); - TERN_(DISABLE_INACTIVE_I, stepper.disable_axis(I_AXIS)); - TERN_(DISABLE_INACTIVE_J, stepper.disable_axis(J_AXIS)); - TERN_(DISABLE_INACTIVE_K, stepper.disable_axis(K_AXIS)); - TERN_(DISABLE_INACTIVE_E, stepper.disable_e_steppers()); - - TERN_(AUTO_BED_LEVELING_UBL, bedlevel.steppers_were_disabled()); + if (!has_blocks && !do_reset_timeout && gcode.stepper_inactive_timeout()) { + if (!already_shutdown_steppers) { + already_shutdown_steppers = true; // L6470 SPI will consume 99% of free time without this + + // Individual axes will be disabled if configured + TERN_(DISABLE_INACTIVE_X, stepper.disable_axis(X_AXIS)); + TERN_(DISABLE_INACTIVE_Y, stepper.disable_axis(Y_AXIS)); + TERN_(DISABLE_INACTIVE_Z, stepper.disable_axis(Z_AXIS)); + TERN_(DISABLE_INACTIVE_I, stepper.disable_axis(I_AXIS)); + TERN_(DISABLE_INACTIVE_J, stepper.disable_axis(J_AXIS)); + TERN_(DISABLE_INACTIVE_K, stepper.disable_axis(K_AXIS)); + TERN_(DISABLE_INACTIVE_E, stepper.disable_e_steppers()); + + TERN_(AUTO_BED_LEVELING_UBL, bedlevel.steppers_were_disabled()); + } } + else + already_shutdown_steppers = false; } - else - already_shutdown_steppers = false; - } + #endif #if ENABLED(PHOTO_GCODE) && PIN_EXISTS(CHDK) // Check if CHDK should be set to LOW (after M240 set it HIGH) diff --git a/Marlin/src/feature/spindle_laser.cpp b/Marlin/src/feature/spindle_laser.cpp index ef3503c40d..8f70816c99 100644 --- a/Marlin/src/feature/spindle_laser.cpp +++ b/Marlin/src/feature/spindle_laser.cpp @@ -39,7 +39,8 @@ #endif SpindleLaser cutter; -uint8_t SpindleLaser::power; +uint8_t SpindleLaser::power, + SpindleLaser::last_power_applied; // = 0 // Basic power state tracking #if ENABLED(LASER_FEATURE) cutter_test_pulse_t SpindleLaser::testPulse = 50; // Test fire Pulse time ms value. #endif @@ -113,7 +114,6 @@ void SpindleLaser::init() { * @param opwr Power value. Range 0 to MAX. When 0 disable spindle/laser. */ void SpindleLaser::apply_power(const uint8_t opwr) { - static uint8_t last_power_applied = 0; if (opwr == last_power_applied) return; last_power_applied = opwr; power = opwr; diff --git a/Marlin/src/feature/spindle_laser.h b/Marlin/src/feature/spindle_laser.h index 1f9bec7bf0..a9dc7794c4 100644 --- a/Marlin/src/feature/spindle_laser.h +++ b/Marlin/src/feature/spindle_laser.h @@ -91,7 +91,8 @@ public: #endif static bool isReady; // Ready to apply power setting from the UI to OCR - static uint8_t power; + static uint8_t power, + last_power_applied; // Basic power state tracking #if ENABLED(MARLIN_DEV_MODE) static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K diff --git a/Marlin/src/gcode/control/M17_M18_M84.cpp b/Marlin/src/gcode/control/M17_M18_M84.cpp index a4738b6359..ac3a602687 100644 --- a/Marlin/src/gcode/control/M17_M18_M84.cpp +++ b/Marlin/src/gcode/control/M17_M18_M84.cpp @@ -213,7 +213,16 @@ void try_to_disable(const stepper_flags_t to_disable) { void GcodeSuite::M18_M84() { if (parser.seenval('S')) { reset_stepper_timeout(); - stepper_inactive_time = parser.value_millis_from_seconds(); + #if HAS_DISABLE_INACTIVE_AXIS + const millis_t ms = parser.value_millis_from_seconds(); + #if LASER_SAFETY_TIMEOUT_MS > 0 + if (ms && ms <= LASER_SAFETY_TIMEOUT_MS) { + SERIAL_ECHO_MSG("M18 timeout must be > ", MS_TO_SEC(LASER_SAFETY_TIMEOUT_MS + 999), " s for laser safety."); + return; + } + #endif + stepper_inactive_time = ms; + #endif } else { if (parser.seen_axis()) { diff --git a/Marlin/src/gcode/control/M3-M5.cpp b/Marlin/src/gcode/control/M3-M5.cpp index 2cf22e81ce..a619b1b762 100644 --- a/Marlin/src/gcode/control/M3-M5.cpp +++ b/Marlin/src/gcode/control/M3-M5.cpp @@ -66,6 +66,10 @@ * PWM duty cycle goes from 0 (off) to 255 (always on). */ void GcodeSuite::M3_M4(const bool is_M4) { + #if LASER_SAFETY_TIMEOUT_MS > 0 + reset_stepper_timeout(); // Reset timeout to allow subsequent G-code to power the laser (imm.) + #endif + #if EITHER(SPINDLE_LASER_USE_PWM, SPINDLE_SERVO) auto get_s_power = [] { if (parser.seenval('S')) { diff --git a/Marlin/src/gcode/control/M85.cpp b/Marlin/src/gcode/control/M85.cpp index 9c8c02c59a..ee868349ed 100644 --- a/Marlin/src/gcode/control/M85.cpp +++ b/Marlin/src/gcode/control/M85.cpp @@ -29,7 +29,14 @@ void GcodeSuite::M85() { if (parser.seen('S')) { reset_stepper_timeout(); - max_inactive_time = parser.value_millis_from_seconds(); + const millis_t ms = parser.value_millis_from_seconds(); + #if LASER_SAFETY_TIMEOUT_MS > 0 + if (ms && ms <= LASER_SAFETY_TIMEOUT_MS) { + SERIAL_ECHO_MSG("M85 timeout must be > ", MS_TO_SEC(LASER_SAFETY_TIMEOUT_MS + 999), " s for laser safety."); + return; + } + #endif + max_inactive_time = ms; } } diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 4f23db2e37..70e2f36f2f 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -73,8 +73,11 @@ GcodeSuite gcode; // Inactivity shutdown millis_t GcodeSuite::previous_move_ms = 0, - GcodeSuite::max_inactive_time = 0, - GcodeSuite::stepper_inactive_time = SEC_TO_MS(DEFAULT_STEPPER_DEACTIVE_TIME); + GcodeSuite::max_inactive_time = 0; + +#if HAS_DISABLE_INACTIVE_AXIS + millis_t GcodeSuite::stepper_inactive_time = SEC_TO_MS(DEFAULT_STEPPER_DEACTIVE_TIME); +#endif // Relative motion mode for each logical axis static constexpr xyze_bool_t ar_init = AXIS_RELATIVE_MODES; diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index fe03879a0c..772accba5d 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -395,14 +395,20 @@ public: static bool select_coordinate_system(const int8_t _new); #endif - static millis_t previous_move_ms, max_inactive_time, stepper_inactive_time; - FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; } + static millis_t previous_move_ms, max_inactive_time; FORCE_INLINE static bool stepper_max_timed_out(const millis_t ms=millis()) { return max_inactive_time && ELAPSED(ms, previous_move_ms + max_inactive_time); } - FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) { - return ELAPSED(ms, previous_move_ms + stepper_inactive_time); - } + FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; } + + #if HAS_DISABLE_INACTIVE_AXIS + static millis_t stepper_inactive_time; + FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) { + return ELAPSED(ms, previous_move_ms + stepper_inactive_time); + } + #else + static bool stepper_inactive_timeout(const millis_t) { return false; } + #endif static void report_echo_start(const bool forReplay); static void report_heading(const bool forReplay, FSTR_P const fstr, const bool eol=true); diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h index 7a0c5dd934..309648a69d 100644 --- a/Marlin/src/inc/Conditionals_adv.h +++ b/Marlin/src/inc/Conditionals_adv.h @@ -1042,3 +1042,7 @@ #undef CONFIGURATION_EMBEDDING #define CANNOT_EMBED_CONFIGURATION defined(__AVR__) #endif + +#if ANY(DISABLE_INACTIVE_X, DISABLE_INACTIVE_Y, DISABLE_INACTIVE_Z, DISABLE_INACTIVE_I, DISABLE_INACTIVE_J, DISABLE_INACTIVE_K, DISABLE_INACTIVE_U, DISABLE_INACTIVE_V, DISABLE_INACTIVE_W, DISABLE_INACTIVE_E) + #define HAS_DISABLE_INACTIVE_AXIS 1 +#endif diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index b4259bf53a..dadbf27273 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -3680,6 +3680,7 @@ static_assert(_PLUS_TEST(4), "HOMING_FEEDRATE_MM_M values must be positive."); #error "Enabled an inline laser feature without inline laser power being enabled." #endif #endif + #define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN) #if BOTH(SPINDLE_FEATURE, LASER_FEATURE) #error "Enable only one of SPINDLE_FEATURE or LASER_FEATURE." @@ -3747,6 +3748,11 @@ static_assert(_PLUS_TEST(4), "HOMING_FEEDRATE_MM_M values must be positive."); #endif #endif #undef _PIN_CONFLICT + + #ifdef LASER_SAFETY_TIMEOUT_MS + static_assert(LASER_SAFETY_TIMEOUT_MS < (DEFAULT_STEPPER_DEACTIVE_TIME) * 1000UL, "LASER_SAFETY_TIMEOUT_MS must be less than DEFAULT_STEPPER_DEACTIVE_TIME (" STRINGIFY(DEFAULT_STEPPER_DEACTIVE_TIME) " seconds)"); + #endif + #endif #if ENABLED(COOLANT_MIST) && !PIN_EXISTS(COOLANT_MIST) diff --git a/Marlin/src/module/temperature.cpp b/Marlin/src/module/temperature.cpp index 2a2b47d40b..ce84b59233 100644 --- a/Marlin/src/module/temperature.cpp +++ b/Marlin/src/module/temperature.cpp @@ -71,6 +71,10 @@ #include "../libs/nozzle.h" #endif +#if LASER_SAFETY_TIMEOUT_MS > 0 + #include "../feature/spindle_laser.h" +#endif + // MAX TC related macros #define TEMP_SENSOR_IS_MAX(n, M) (ENABLED(TEMP_SENSOR_##n##_IS_MAX##M) || (ENABLED(TEMP_SENSOR_REDUNDANT_IS_MAX##M) && REDUNDANT_TEMP_MATCH(SOURCE, E##n))) #define TEMP_SENSOR_IS_ANY_MAX_TC(n) (ENABLED(TEMP_SENSOR_##n##_IS_MAX_TC) || (ENABLED(TEMP_SENSOR_REDUNDANT_IS_MAX_TC) && REDUNDANT_TEMP_MATCH(SOURCE, E##n))) @@ -3325,6 +3329,7 @@ public: /** * Handle various ~1kHz tasks associated with temperature + * - Check laser safety timeout * - Heater PWM (~1kHz with scaler) * - LCD Button polling (~500Hz) * - Start / Read one ADC sensor @@ -3334,6 +3339,14 @@ public: */ void Temperature::isr() { + // Shut down the laser if steppers are inactive for > LASER_SAFETY_TIMEOUT_MS ms + #if LASER_SAFETY_TIMEOUT_MS > 0 + if (cutter.last_power_applied && ELAPSED(millis(), gcode.previous_move_ms + (LASER_SAFETY_TIMEOUT_MS))) { + cutter.power = 0; // Prevent planner idle from re-enabling power + cutter.apply_power(0); + } + #endif + static int8_t temp_count = -1; static ADCSensorState adc_sensor_state = StartupDelay; static uint8_t pwm_count = _BV(SOFT_PWM_SCALE); diff --git a/buildroot/tests/BIGTREE_SKR_PRO b/buildroot/tests/BIGTREE_SKR_PRO index 2503b28544..2f1075c7ef 100755 --- a/buildroot/tests/BIGTREE_SKR_PRO +++ b/buildroot/tests/BIGTREE_SKR_PRO @@ -26,7 +26,7 @@ opt_set MOTHERBOARD BOARD_BTT_SKR_PRO_V1_1 SERIAL_PORT -1 \ CUTTER_POWER_UNIT PERCENT \ SPINDLE_LASER_PWM_PIN HEATER_1_PIN SPINDLE_LASER_ENA_PIN HEATER_2_PIN \ TEMP_SENSOR_COOLER 1000 TEMP_COOLER_PIN PD13 -opt_enable LASER_FEATURE REPRAP_DISCOUNT_SMART_CONTROLLER +opt_enable LASER_FEATURE LASER_SAFETY_TIMEOUT_MS REPRAP_DISCOUNT_SMART_CONTROLLER exec_test $1 $2 "BigTreeTech SKR Pro | Laser (Percent) | Cooling | LCD" "$3" # clean up diff --git a/buildroot/tests/mega2560 b/buildroot/tests/mega2560 index 3677abf60e..fc24cf2125 100755 --- a/buildroot/tests/mega2560 +++ b/buildroot/tests/mega2560 @@ -179,8 +179,8 @@ opt_set MOTHERBOARD BOARD_RAMPS_14_EFB EXTRUDERS 0 LCD_LANGUAGE en TEMP_SENSOR_C DEFAULT_MAX_ACCELERATION '{ 3000, 3000, 100 }' \ MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \ AXIS_RELATIVE_MODES '{ false, false, false }' -opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT \ - LASER_FEATURE AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN LASER_COOLANT_FLOW_METER MEATPACK_ON_SERIAL_PORT_1 +opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT MEATPACK_ON_SERIAL_PORT_1 \ + LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 12864 LCD | meatpack | SERIAL_PORT_2 " "$3" # @@ -193,8 +193,8 @@ opt_set MOTHERBOARD BOARD_RAMPS_14_EFB EXTRUDERS 0 LCD_LANGUAGE en TEMP_SENSOR_C DEFAULT_MAX_ACCELERATION '{ 3000, 3000, 100 }' \ MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \ AXIS_RELATIVE_MODES '{ false, false, false }' -opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER \ - LASER_FEATURE AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN LASER_COOLANT_FLOW_METER I2C_AMMETER +opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER I2C_AMMETER \ + LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 44780 LCD " "$3" #