Browse Source

✨ Laser Safety Timeout (#24189)

John Robertson 2 years ago
parent
commit
85c0875db2
No account linked to committer's email address

+ 10
- 0
Marlin/Configuration_adv.h View File

@@ -3668,6 +3668,16 @@
3668 3668
     #define LASER_TEST_PULSE_MIN           1   // Used with Laser Control Menu
3669 3669
     #define LASER_TEST_PULSE_MAX         999   // Caution: Menu may not show more than 3 characters
3670 3670
 
3671
+   /**
3672
+    * Laser Safety Timeout
3673
+    *
3674
+    * The laser should be turned off when there is no movement for a period of time.
3675
+    * Consider material flammability, cut rate, and G-code order when setting this
3676
+    * value. Too low and it could turn off during a very slow move; too high and
3677
+    * the material could ignite.
3678
+    */
3679
+    #define LASER_SAFETY_TIMEOUT_MS     1000   // (ms)
3680
+
3671 3681
     /**
3672 3682
      * Enable inline laser power to be handled in the planner / stepper routines.
3673 3683
      * Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I)

+ 29
- 28
Marlin/src/MarlinCore.cpp View File

@@ -423,37 +423,38 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) {
423 423
     kill();
424 424
   }
425 425
 
426
-  // M18 / M84 : Handle steppers inactive time timeout
427
-  if (gcode.stepper_inactive_time) {
428
-
429
-    static bool already_shutdown_steppers; // = false
426
+  const bool has_blocks = planner.has_blocks_queued();  // Any moves in the planner?
427
+  if (has_blocks) gcode.reset_stepper_timeout(ms);      // Reset timeout for M18/M84, M85 max 'kill', and laser.
430 428
 
431
-    // Any moves in the planner? Resets both the M18/M84
432
-    // activity timeout and the M85 max 'kill' timeout
433
-    if (planner.has_blocks_queued())
434
-      gcode.reset_stepper_timeout(ms);
435
-    else if (!do_reset_timeout && gcode.stepper_inactive_timeout()) {
436
-      if (!already_shutdown_steppers) {
437
-        already_shutdown_steppers = true;  // L6470 SPI will consume 99% of free time without this
438
-
439
-        // Individual axes will be disabled if configured
440
-        TERN_(DISABLE_INACTIVE_X, stepper.disable_axis(X_AXIS));
441
-        TERN_(DISABLE_INACTIVE_Y, stepper.disable_axis(Y_AXIS));
442
-        TERN_(DISABLE_INACTIVE_Z, stepper.disable_axis(Z_AXIS));
443
-        TERN_(DISABLE_INACTIVE_I, stepper.disable_axis(I_AXIS));
444
-        TERN_(DISABLE_INACTIVE_J, stepper.disable_axis(J_AXIS));
445
-        TERN_(DISABLE_INACTIVE_K, stepper.disable_axis(K_AXIS));
446
-        TERN_(DISABLE_INACTIVE_U, stepper.disable_axis(U_AXIS));
447
-        TERN_(DISABLE_INACTIVE_V, stepper.disable_axis(V_AXIS));
448
-        TERN_(DISABLE_INACTIVE_W, stepper.disable_axis(W_AXIS));
449
-        TERN_(DISABLE_INACTIVE_E, stepper.disable_e_steppers());
450
-
451
-        TERN_(AUTO_BED_LEVELING_UBL, bedlevel.steppers_were_disabled());
429
+  // M18 / M84 : Handle steppers inactive time timeout
430
+  #if HAS_DISABLE_INACTIVE_AXIS
431
+    if (gcode.stepper_inactive_time) {
432
+
433
+      static bool already_shutdown_steppers; // = false
434
+
435
+      if (!has_blocks && !do_reset_timeout && gcode.stepper_inactive_timeout()) {
436
+        if (!already_shutdown_steppers) {
437
+          already_shutdown_steppers = true;  // L6470 SPI will consume 99% of free time without this
438
+
439
+          // Individual axes will be disabled if configured
440
+          TERN_(DISABLE_INACTIVE_X, stepper.disable_axis(X_AXIS));
441
+          TERN_(DISABLE_INACTIVE_Y, stepper.disable_axis(Y_AXIS));
442
+          TERN_(DISABLE_INACTIVE_Z, stepper.disable_axis(Z_AXIS));
443
+          TERN_(DISABLE_INACTIVE_I, stepper.disable_axis(I_AXIS));
444
+          TERN_(DISABLE_INACTIVE_J, stepper.disable_axis(J_AXIS));
445
+          TERN_(DISABLE_INACTIVE_K, stepper.disable_axis(K_AXIS));
446
+          TERN_(DISABLE_INACTIVE_U, stepper.disable_axis(U_AXIS));
447
+          TERN_(DISABLE_INACTIVE_V, stepper.disable_axis(V_AXIS));
448
+          TERN_(DISABLE_INACTIVE_W, stepper.disable_axis(W_AXIS));
449
+          TERN_(DISABLE_INACTIVE_E, stepper.disable_e_steppers());
450
+
451
+          TERN_(AUTO_BED_LEVELING_UBL, bedlevel.steppers_were_disabled());
452
+        }
452 453
       }
454
+      else
455
+        already_shutdown_steppers = false;
453 456
     }
454
-    else
455
-      already_shutdown_steppers = false;
456
-  }
457
+  #endif
457 458
 
458 459
   #if ENABLED(PHOTO_GCODE) && PIN_EXISTS(CHDK)
459 460
     // Check if CHDK should be set to LOW (after M240 set it HIGH)

+ 2
- 2
Marlin/src/feature/spindle_laser.cpp View File

@@ -39,7 +39,8 @@
39 39
 #endif
40 40
 
41 41
 SpindleLaser cutter;
42
-uint8_t SpindleLaser::power;
42
+uint8_t SpindleLaser::power,
43
+        SpindleLaser::last_power_applied; // = 0                      // Basic power state tracking
43 44
 #if ENABLED(LASER_FEATURE)
44 45
   cutter_test_pulse_t SpindleLaser::testPulse = 50;                   // Test fire Pulse time ms value.
45 46
 #endif
@@ -113,7 +114,6 @@ void SpindleLaser::init() {
113 114
  * @param opwr Power value. Range 0 to MAX. When 0 disable spindle/laser.
114 115
  */
115 116
 void SpindleLaser::apply_power(const uint8_t opwr) {
116
-  static uint8_t last_power_applied = 0;
117 117
   if (opwr == last_power_applied) return;
118 118
   last_power_applied = opwr;
119 119
   power = opwr;

+ 2
- 1
Marlin/src/feature/spindle_laser.h View File

@@ -89,7 +89,8 @@ public:
89 89
   #endif
90 90
 
91 91
   static bool isReady;                    // Ready to apply power setting from the UI to OCR
92
-  static uint8_t power;
92
+  static uint8_t power,
93
+                 last_power_applied;      // Basic power state tracking
93 94
 
94 95
   #if ENABLED(MARLIN_DEV_MODE)
95 96
     static cutter_frequency_t frequency;  // Set PWM frequency; range: 2K-50K

+ 10
- 1
Marlin/src/gcode/control/M17_M18_M84.cpp View File

@@ -211,7 +211,16 @@ void try_to_disable(const stepper_flags_t to_disable) {
211 211
 void GcodeSuite::M18_M84() {
212 212
   if (parser.seenval('S')) {
213 213
     reset_stepper_timeout();
214
-    stepper_inactive_time = parser.value_millis_from_seconds();
214
+    #if HAS_DISABLE_INACTIVE_AXIS
215
+      const millis_t ms = parser.value_millis_from_seconds();
216
+      #if LASER_SAFETY_TIMEOUT_MS > 0
217
+        if (ms && ms <= LASER_SAFETY_TIMEOUT_MS) {
218
+          SERIAL_ECHO_MSG("M18 timeout must be > ", MS_TO_SEC(LASER_SAFETY_TIMEOUT_MS + 999), " s for laser safety.");
219
+          return;
220
+        }
221
+      #endif
222
+      stepper_inactive_time = ms;
223
+    #endif
215 224
   }
216 225
   else {
217 226
     if (parser.seen_axis()) {

+ 4
- 0
Marlin/src/gcode/control/M3-M5.cpp View File

@@ -66,6 +66,10 @@
66 66
  *  PWM duty cycle goes from 0 (off) to 255 (always on).
67 67
  */
68 68
 void GcodeSuite::M3_M4(const bool is_M4) {
69
+  #if LASER_SAFETY_TIMEOUT_MS > 0
70
+    reset_stepper_timeout(); // Reset timeout to allow subsequent G-code to power the laser (imm.)
71
+  #endif
72
+
69 73
   #if EITHER(SPINDLE_LASER_USE_PWM, SPINDLE_SERVO)
70 74
     auto get_s_power = [] {
71 75
       if (parser.seenval('S')) {

+ 8
- 1
Marlin/src/gcode/control/M85.cpp View File

@@ -29,7 +29,14 @@ void GcodeSuite::M85() {
29 29
 
30 30
   if (parser.seen('S')) {
31 31
     reset_stepper_timeout();
32
-    max_inactive_time = parser.value_millis_from_seconds();
32
+    const millis_t ms = parser.value_millis_from_seconds();
33
+    #if LASER_SAFETY_TIMEOUT_MS > 0
34
+      if (ms && ms <= LASER_SAFETY_TIMEOUT_MS) {
35
+        SERIAL_ECHO_MSG("M85 timeout must be > ", MS_TO_SEC(LASER_SAFETY_TIMEOUT_MS + 999), " s for laser safety.");
36
+        return;
37
+      }
38
+    #endif
39
+    max_inactive_time = ms;
33 40
   }
34 41
 
35 42
 }

+ 5
- 2
Marlin/src/gcode/gcode.cpp View File

@@ -73,8 +73,11 @@ GcodeSuite gcode;
73 73
 
74 74
 // Inactivity shutdown
75 75
 millis_t GcodeSuite::previous_move_ms = 0,
76
-         GcodeSuite::max_inactive_time = 0,
77
-         GcodeSuite::stepper_inactive_time = SEC_TO_MS(DEFAULT_STEPPER_DEACTIVE_TIME);
76
+         GcodeSuite::max_inactive_time = 0;
77
+
78
+#if HAS_DISABLE_INACTIVE_AXIS
79
+  millis_t GcodeSuite::stepper_inactive_time = SEC_TO_MS(DEFAULT_STEPPER_DEACTIVE_TIME);
80
+#endif
78 81
 
79 82
 // Relative motion mode for each logical axis
80 83
 static constexpr xyze_bool_t ar_init = AXIS_RELATIVE_MODES;

+ 11
- 5
Marlin/src/gcode/gcode.h View File

@@ -396,14 +396,20 @@ public:
396 396
     static bool select_coordinate_system(const int8_t _new);
397 397
   #endif
398 398
 
399
-  static millis_t previous_move_ms, max_inactive_time, stepper_inactive_time;
400
-  FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; }
399
+  static millis_t previous_move_ms, max_inactive_time;
401 400
   FORCE_INLINE static bool stepper_max_timed_out(const millis_t ms=millis()) {
402 401
     return max_inactive_time && ELAPSED(ms, previous_move_ms + max_inactive_time);
403 402
   }
404
-  FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) {
405
-    return ELAPSED(ms, previous_move_ms + stepper_inactive_time);
406
-  }
403
+  FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; }
404
+
405
+  #if HAS_DISABLE_INACTIVE_AXIS
406
+    static millis_t stepper_inactive_time;
407
+    FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) {
408
+      return ELAPSED(ms, previous_move_ms + stepper_inactive_time);
409
+    }
410
+  #else
411
+    static bool stepper_inactive_timeout(const millis_t) { return false; }
412
+  #endif
407 413
 
408 414
   static void report_echo_start(const bool forReplay);
409 415
   static void report_heading(const bool forReplay, FSTR_P const fstr, const bool eol=true);

+ 4
- 0
Marlin/src/inc/Conditionals_adv.h View File

@@ -1044,3 +1044,7 @@
1044 1044
   #undef CONFIGURATION_EMBEDDING
1045 1045
   #define CANNOT_EMBED_CONFIGURATION defined(__AVR__)
1046 1046
 #endif
1047
+
1048
+#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)
1049
+  #define HAS_DISABLE_INACTIVE_AXIS 1
1050
+#endif

+ 6
- 0
Marlin/src/inc/SanityCheck.h View File

@@ -3867,6 +3867,7 @@ static_assert(_PLUS_TEST(4), "HOMING_FEEDRATE_MM_M values must be positive.");
3867 3867
       #error "Enabled an inline laser feature without inline laser power being enabled."
3868 3868
     #endif
3869 3869
   #endif
3870
+
3870 3871
   #define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN)
3871 3872
   #if BOTH(SPINDLE_FEATURE, LASER_FEATURE)
3872 3873
     #error "Enable only one of SPINDLE_FEATURE or LASER_FEATURE."
@@ -3934,6 +3935,11 @@ static_assert(_PLUS_TEST(4), "HOMING_FEEDRATE_MM_M values must be positive.");
3934 3935
     #endif
3935 3936
   #endif
3936 3937
   #undef _PIN_CONFLICT
3938
+
3939
+  #ifdef LASER_SAFETY_TIMEOUT_MS
3940
+    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)");
3941
+  #endif
3942
+
3937 3943
 #endif
3938 3944
 
3939 3945
 #if ENABLED(COOLANT_MIST) && !PIN_EXISTS(COOLANT_MIST)

+ 13
- 0
Marlin/src/module/temperature.cpp View File

@@ -71,6 +71,10 @@
71 71
   #include "../libs/nozzle.h"
72 72
 #endif
73 73
 
74
+#if LASER_SAFETY_TIMEOUT_MS > 0
75
+  #include "../feature/spindle_laser.h"
76
+#endif
77
+
74 78
 // MAX TC related macros
75 79
 #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)))
76 80
 #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)))
@@ -3328,6 +3332,7 @@ public:
3328 3332
 
3329 3333
 /**
3330 3334
  * Handle various ~1kHz tasks associated with temperature
3335
+ *  - Check laser safety timeout
3331 3336
  *  - Heater PWM (~1kHz with scaler)
3332 3337
  *  - LCD Button polling (~500Hz)
3333 3338
  *  - Start / Read one ADC sensor
@@ -3337,6 +3342,14 @@ public:
3337 3342
  */
3338 3343
 void Temperature::isr() {
3339 3344
 
3345
+  // Shut down the laser if steppers are inactive for > LASER_SAFETY_TIMEOUT_MS ms
3346
+  #if LASER_SAFETY_TIMEOUT_MS > 0
3347
+    if (cutter.last_power_applied && ELAPSED(millis(), gcode.previous_move_ms + (LASER_SAFETY_TIMEOUT_MS))) {
3348
+      cutter.power = 0;       // Prevent planner idle from re-enabling power
3349
+      cutter.apply_power(0);
3350
+    }
3351
+  #endif
3352
+
3340 3353
   static int8_t temp_count = -1;
3341 3354
   static ADCSensorState adc_sensor_state = StartupDelay;
3342 3355
   static uint8_t pwm_count = _BV(SOFT_PWM_SCALE);

+ 1
- 1
buildroot/tests/BIGTREE_SKR_PRO View File

@@ -26,7 +26,7 @@ opt_set MOTHERBOARD BOARD_BTT_SKR_PRO_V1_1 SERIAL_PORT -1 \
26 26
         CUTTER_POWER_UNIT PERCENT \
27 27
         SPINDLE_LASER_PWM_PIN HEATER_1_PIN SPINDLE_LASER_ENA_PIN HEATER_2_PIN \
28 28
         TEMP_SENSOR_COOLER 1000 TEMP_COOLER_PIN PD13
29
-opt_enable LASER_FEATURE REPRAP_DISCOUNT_SMART_CONTROLLER
29
+opt_enable LASER_FEATURE LASER_SAFETY_TIMEOUT_MS REPRAP_DISCOUNT_SMART_CONTROLLER
30 30
 exec_test $1 $2 "BigTreeTech SKR Pro | Laser (Percent) | Cooling | LCD" "$3"
31 31
 
32 32
 # clean up

+ 4
- 4
buildroot/tests/mega2560 View File

@@ -179,8 +179,8 @@ opt_set MOTHERBOARD BOARD_RAMPS_14_EFB EXTRUDERS 0 LCD_LANGUAGE en TEMP_SENSOR_C
179 179
         DEFAULT_MAX_ACCELERATION '{ 3000, 3000, 100 }' \
180 180
         MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \
181 181
         AXIS_RELATIVE_MODES '{ false, false, false }'
182
-opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT \
183
-           LASER_FEATURE AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN LASER_COOLANT_FLOW_METER MEATPACK_ON_SERIAL_PORT_1
182
+opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT MEATPACK_ON_SERIAL_PORT_1 \
183
+           LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN
184 184
 exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 12864 LCD | meatpack | SERIAL_PORT_2 " "$3"
185 185
 
186 186
 #
@@ -193,8 +193,8 @@ opt_set MOTHERBOARD BOARD_RAMPS_14_EFB EXTRUDERS 0 LCD_LANGUAGE en TEMP_SENSOR_C
193 193
         DEFAULT_MAX_ACCELERATION '{ 3000, 3000, 100 }' \
194 194
         MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \
195 195
         AXIS_RELATIVE_MODES '{ false, false, false }'
196
-opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER \
197
-           LASER_FEATURE AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN LASER_COOLANT_FLOW_METER I2C_AMMETER
196
+opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER I2C_AMMETER \
197
+           LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN
198 198
 exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 44780 LCD " "$3"
199 199
 
200 200
 #

Loading…
Cancel
Save