✨ Laser Safety Timeout (#24189)
This commit is contained in:
		
				
					committed by
					
						 Scott Lahteine
						Scott Lahteine
					
				
			
			
				
	
			
			
			
						parent
						
							07cd248b91
						
					
				
				
					commit
					9a74bcd4cf
				
			| @@ -3544,6 +3544,16 @@ | |||||||
|     #define LASER_TEST_PULSE_MIN           1   // Used with Laser Control Menu |     #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 |     #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. |      * 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) |      * Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I) | ||||||
|   | |||||||
| @@ -423,16 +423,16 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { | |||||||
|     kill(); |     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 |   // M18 / M84 : Handle steppers inactive time timeout | ||||||
|  |   #if HAS_DISABLE_INACTIVE_AXIS | ||||||
|     if (gcode.stepper_inactive_time) { |     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 |       if (!has_blocks && !do_reset_timeout && gcode.stepper_inactive_timeout()) { | ||||||
|     // 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) { |         if (!already_shutdown_steppers) { | ||||||
|           already_shutdown_steppers = true;  // L6470 SPI will consume 99% of free time without this |           already_shutdown_steppers = true;  // L6470 SPI will consume 99% of free time without this | ||||||
|  |  | ||||||
| @@ -451,6 +451,7 @@ inline void manage_inactivity(const bool no_stepper_sleep=false) { | |||||||
|       else |       else | ||||||
|         already_shutdown_steppers = false; |         already_shutdown_steppers = false; | ||||||
|     } |     } | ||||||
|  |   #endif | ||||||
|  |  | ||||||
|   #if ENABLED(PHOTO_GCODE) && PIN_EXISTS(CHDK) |   #if ENABLED(PHOTO_GCODE) && PIN_EXISTS(CHDK) | ||||||
|     // Check if CHDK should be set to LOW (after M240 set it HIGH) |     // Check if CHDK should be set to LOW (after M240 set it HIGH) | ||||||
|   | |||||||
| @@ -39,7 +39,8 @@ | |||||||
| #endif | #endif | ||||||
|  |  | ||||||
| SpindleLaser cutter; | SpindleLaser cutter; | ||||||
| uint8_t SpindleLaser::power; | uint8_t SpindleLaser::power, | ||||||
|  |         SpindleLaser::last_power_applied; // = 0                      // Basic power state tracking | ||||||
| #if ENABLED(LASER_FEATURE) | #if ENABLED(LASER_FEATURE) | ||||||
|   cutter_test_pulse_t SpindleLaser::testPulse = 50;                   // Test fire Pulse time ms value. |   cutter_test_pulse_t SpindleLaser::testPulse = 50;                   // Test fire Pulse time ms value. | ||||||
| #endif | #endif | ||||||
| @@ -113,7 +114,6 @@ void SpindleLaser::init() { | |||||||
|  * @param opwr Power value. Range 0 to MAX. When 0 disable spindle/laser. |  * @param opwr Power value. Range 0 to MAX. When 0 disable spindle/laser. | ||||||
|  */ |  */ | ||||||
| void SpindleLaser::apply_power(const uint8_t opwr) { | void SpindleLaser::apply_power(const uint8_t opwr) { | ||||||
|   static uint8_t last_power_applied = 0; |  | ||||||
|   if (opwr == last_power_applied) return; |   if (opwr == last_power_applied) return; | ||||||
|   last_power_applied = opwr; |   last_power_applied = opwr; | ||||||
|   power = opwr; |   power = opwr; | ||||||
|   | |||||||
| @@ -91,7 +91,8 @@ public: | |||||||
|   #endif |   #endif | ||||||
|  |  | ||||||
|   static bool isReady;                    // Ready to apply power setting from the UI to OCR |   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) |   #if ENABLED(MARLIN_DEV_MODE) | ||||||
|     static cutter_frequency_t frequency;  // Set PWM frequency; range: 2K-50K |     static cutter_frequency_t frequency;  // Set PWM frequency; range: 2K-50K | ||||||
|   | |||||||
| @@ -213,7 +213,16 @@ void try_to_disable(const stepper_flags_t to_disable) { | |||||||
| void GcodeSuite::M18_M84() { | void GcodeSuite::M18_M84() { | ||||||
|   if (parser.seenval('S')) { |   if (parser.seenval('S')) { | ||||||
|     reset_stepper_timeout(); |     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 { |   else { | ||||||
|     if (parser.seen_axis()) { |     if (parser.seen_axis()) { | ||||||
|   | |||||||
| @@ -66,6 +66,10 @@ | |||||||
|  *  PWM duty cycle goes from 0 (off) to 255 (always on). |  *  PWM duty cycle goes from 0 (off) to 255 (always on). | ||||||
|  */ |  */ | ||||||
| void GcodeSuite::M3_M4(const bool is_M4) { | 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) |   #if EITHER(SPINDLE_LASER_USE_PWM, SPINDLE_SERVO) | ||||||
|     auto get_s_power = [] { |     auto get_s_power = [] { | ||||||
|       if (parser.seenval('S')) { |       if (parser.seenval('S')) { | ||||||
|   | |||||||
| @@ -29,7 +29,14 @@ void GcodeSuite::M85() { | |||||||
|  |  | ||||||
|   if (parser.seen('S')) { |   if (parser.seen('S')) { | ||||||
|     reset_stepper_timeout(); |     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; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -73,8 +73,11 @@ GcodeSuite gcode; | |||||||
|  |  | ||||||
| // Inactivity shutdown | // Inactivity shutdown | ||||||
| millis_t GcodeSuite::previous_move_ms = 0, | millis_t GcodeSuite::previous_move_ms = 0, | ||||||
|          GcodeSuite::max_inactive_time = 0, |          GcodeSuite::max_inactive_time = 0; | ||||||
|          GcodeSuite::stepper_inactive_time = SEC_TO_MS(DEFAULT_STEPPER_DEACTIVE_TIME); |  | ||||||
|  | #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 | // Relative motion mode for each logical axis | ||||||
| static constexpr xyze_bool_t ar_init = AXIS_RELATIVE_MODES; | static constexpr xyze_bool_t ar_init = AXIS_RELATIVE_MODES; | ||||||
|   | |||||||
| @@ -395,14 +395,20 @@ public: | |||||||
|     static bool select_coordinate_system(const int8_t _new); |     static bool select_coordinate_system(const int8_t _new); | ||||||
|   #endif |   #endif | ||||||
|  |  | ||||||
|   static millis_t previous_move_ms, max_inactive_time, stepper_inactive_time; |   static millis_t previous_move_ms, max_inactive_time; | ||||||
|   FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; } |  | ||||||
|   FORCE_INLINE static bool stepper_max_timed_out(const millis_t ms=millis()) { |   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); |     return max_inactive_time && ELAPSED(ms, previous_move_ms + max_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()) { |     FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) { | ||||||
|       return ELAPSED(ms, previous_move_ms + stepper_inactive_time); |       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_echo_start(const bool forReplay); | ||||||
|   static void report_heading(const bool forReplay, FSTR_P const fstr, const bool eol=true); |   static void report_heading(const bool forReplay, FSTR_P const fstr, const bool eol=true); | ||||||
|   | |||||||
| @@ -1042,3 +1042,7 @@ | |||||||
|   #undef CONFIGURATION_EMBEDDING |   #undef CONFIGURATION_EMBEDDING | ||||||
|   #define CANNOT_EMBED_CONFIGURATION defined(__AVR__) |   #define CANNOT_EMBED_CONFIGURATION defined(__AVR__) | ||||||
| #endif | #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 | ||||||
|   | |||||||
| @@ -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." |       #error "Enabled an inline laser feature without inline laser power being enabled." | ||||||
|     #endif |     #endif | ||||||
|   #endif |   #endif | ||||||
|  |  | ||||||
|   #define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN) |   #define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN) | ||||||
|   #if BOTH(SPINDLE_FEATURE, LASER_FEATURE) |   #if BOTH(SPINDLE_FEATURE, LASER_FEATURE) | ||||||
|     #error "Enable only one of SPINDLE_FEATURE or 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 | ||||||
|   #endif |   #endif | ||||||
|   #undef _PIN_CONFLICT |   #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 | #endif | ||||||
|  |  | ||||||
| #if ENABLED(COOLANT_MIST) && !PIN_EXISTS(COOLANT_MIST) | #if ENABLED(COOLANT_MIST) && !PIN_EXISTS(COOLANT_MIST) | ||||||
|   | |||||||
| @@ -71,6 +71,10 @@ | |||||||
|   #include "../libs/nozzle.h" |   #include "../libs/nozzle.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #if LASER_SAFETY_TIMEOUT_MS > 0 | ||||||
|  |   #include "../feature/spindle_laser.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| // MAX TC related macros | // 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_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))) | #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 |  * Handle various ~1kHz tasks associated with temperature | ||||||
|  |  *  - Check laser safety timeout | ||||||
|  *  - Heater PWM (~1kHz with scaler) |  *  - Heater PWM (~1kHz with scaler) | ||||||
|  *  - LCD Button polling (~500Hz) |  *  - LCD Button polling (~500Hz) | ||||||
|  *  - Start / Read one ADC sensor |  *  - Start / Read one ADC sensor | ||||||
| @@ -3334,6 +3339,14 @@ public: | |||||||
|  */ |  */ | ||||||
| void Temperature::isr() { | 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 int8_t temp_count = -1; | ||||||
|   static ADCSensorState adc_sensor_state = StartupDelay; |   static ADCSensorState adc_sensor_state = StartupDelay; | ||||||
|   static uint8_t pwm_count = _BV(SOFT_PWM_SCALE); |   static uint8_t pwm_count = _BV(SOFT_PWM_SCALE); | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ opt_set MOTHERBOARD BOARD_BTT_SKR_PRO_V1_1 SERIAL_PORT -1 \ | |||||||
|         CUTTER_POWER_UNIT PERCENT \ |         CUTTER_POWER_UNIT PERCENT \ | ||||||
|         SPINDLE_LASER_PWM_PIN HEATER_1_PIN SPINDLE_LASER_ENA_PIN HEATER_2_PIN \ |         SPINDLE_LASER_PWM_PIN HEATER_1_PIN SPINDLE_LASER_ENA_PIN HEATER_2_PIN \ | ||||||
|         TEMP_SENSOR_COOLER 1000 TEMP_COOLER_PIN PD13 |         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" | exec_test $1 $2 "BigTreeTech SKR Pro | Laser (Percent) | Cooling | LCD" "$3" | ||||||
|  |  | ||||||
| # clean up | # clean up | ||||||
|   | |||||||
| @@ -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 }' \ |         DEFAULT_MAX_ACCELERATION '{ 3000, 3000, 100 }' \ | ||||||
|         MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \ |         MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \ | ||||||
|         AXIS_RELATIVE_MODES '{ false, false, false }' |         AXIS_RELATIVE_MODES '{ false, false, false }' | ||||||
| opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT \ | opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT MEATPACK_ON_SERIAL_PORT_1 \ | ||||||
|            LASER_FEATURE AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN LASER_COOLANT_FLOW_METER 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" | 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 }' \ |         DEFAULT_MAX_ACCELERATION '{ 3000, 3000, 100 }' \ | ||||||
|         MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \ |         MANUAL_FEEDRATE '{ 50*60, 50*60, 4*60 }' \ | ||||||
|         AXIS_RELATIVE_MODES '{ false, false, false }' |         AXIS_RELATIVE_MODES '{ false, false, false }' | ||||||
| opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER \ | opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER I2C_AMMETER \ | ||||||
|            LASER_FEATURE AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN LASER_COOLANT_FLOW_METER 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" | exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 44780 LCD " "$3" | ||||||
|  |  | ||||||
| # | # | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user