diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 00f89662c7..7d1547c0d0 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -2662,31 +2662,123 @@ #define SPINDLE_LASER_ACTIVE_HIGH false // Set to "true" if the on/off function is active HIGH #define SPINDLE_LASER_PWM true // Set to "true" if your controller supports setting the speed/power #define SPINDLE_LASER_PWM_INVERT true // Set to "true" if the speed/power goes up when you want it to go slower - #define SPINDLE_LASER_POWERUP_DELAY 5000 // (ms) Delay to allow the spindle/laser to come up to speed/power - #define SPINDLE_LASER_POWERDOWN_DELAY 5000 // (ms) Delay to allow the spindle to stop + + #define SPINDLE_LASER_FREQUENCY 2500 // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC) + + /** + * Speed / Power can be set ('M3 S') and displayed in terms of: + * - PWM (S0 - S255) + * - PERCENT (S0 - S100) + * - RPM (S0 - S50000) Best for use with a spindle + */ + #define CUTTER_POWER_DISPLAY PWM + + /** + * Relative mode uses relative range (SPEED_POWER_MIN to SPEED_POWER_MAX) instead of normal range (0 to SPEED_POWER_MAX) + * Best use with SuperPID router controller where for example S0 = 5,000 RPM and S255 = 30,000 RPM + */ + //#define CUTTER_POWER_RELATIVE // Set speed proportional to [SPEED_POWER_MIN...SPEED_POWER_MAX] instead of directly #if ENABLED(SPINDLE_FEATURE) //#define SPINDLE_CHANGE_DIR // Enable if your spindle controller can change spindle direction #define SPINDLE_CHANGE_DIR_STOP // Enable if the spindle should stop before changing spin direction #define SPINDLE_INVERT_DIR false // Set to "true" if the spin direction is reversed + #define SPINDLE_LASER_POWERUP_DELAY 5000 // (ms) Delay to allow the spindle/laser to come up to speed/power + #define SPINDLE_LASER_POWERDOWN_DELAY 5000 // (ms) Delay to allow the spindle to stop + /** - * The M3 & M4 commands use the following equation to convert PWM duty cycle to speed/power + * M3/M4 uses the following equation to convert speed/power to PWM duty cycle + * Power = ((DC / 255 * 100) - SPEED_POWER_INTERCEPT)) * (1 / SPEED_POWER_SLOPE) + * where PWM DC varies from 0 to 255 * - * SPEED/POWER = PWM duty cycle * SPEED_POWER_SLOPE + SPEED_POWER_INTERCEPT - * where PWM duty cycle varies from 0 to 255 - * - * set the following for your controller (ALL MUST BE SET) + * Set these required parameters for your controller */ - #define SPEED_POWER_SLOPE 118.4 - #define SPEED_POWER_INTERCEPT 0 - #define SPEED_POWER_MIN 5000 - #define SPEED_POWER_MAX 30000 // SuperPID router controller 0 - 30,000 RPM + #define SPEED_POWER_SLOPE 118.4 // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255 + #define SPEED_POWER_INTERCEPT 0 + #define SPEED_POWER_MIN 5000 + #define SPEED_POWER_MAX 30000 // SuperPID router controller 0 - 30,000 RPM + #define SPEED_POWER_STARTUP 25000 // The default value for speed power when M3 is called without arguments + #else - #define SPEED_POWER_SLOPE 0.3922 - #define SPEED_POWER_INTERCEPT 0 - #define SPEED_POWER_MIN 10 - #define SPEED_POWER_MAX 100 // 0-100% + + #define SPEED_POWER_SLOPE 0.3922 // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255 + #define SPEED_POWER_INTERCEPT 0 + #define SPEED_POWER_MIN 0 + #define SPEED_POWER_MAX 100 // 0-100% + #define SPEED_POWER_STARTUP 80 // The default value for speed power when M3 is called without arguments + + /** + * 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) + * or by the 'S' parameter in G0/G1/G2/G3 moves (see LASER_MOVE_POWER). + * + * This allows the laser to keep in perfect sync with the planner and removes + * the powerup/down delay since lasers require negligible time. + */ + #define LASER_POWER_INLINE + + #if ENABLED(LASER_POWER_INLINE) + /** + * Scale the laser's power in proportion to the movement rate. + * + * - Sets the entry power proportional to the entry speed over the nominal speed. + * - Ramps the power up every N steps to approximate the speed trapezoid. + * - Due to the limited power resolution this is only approximate. + */ + #define LASER_POWER_INLINE_TRAPEZOID + + /** + * Continuously calculate the current power (nominal_power * current_rate / nominal_rate). + * Required for accurate power with non-trapezoidal acceleration (e.g., S_CURVE_ACCELERATION). + * This is a costly calculation so this option is discouraged on 8-bit AVR boards. + * + * LASER_POWER_INLINE_TRAPEZOID_CONT_PER defines how many step cycles there are between power updates. If your + * board isn't able to generate steps fast enough (and you are using LASER_POWER_INLINE_TRAPEZOID_CONT), increase this. + * Note that when this is zero it means it occurs every cycle; 1 means a delay wait one cycle then run, etc. + */ + //#define LASER_POWER_INLINE_TRAPEZOID_CONT + + /** + * Stepper iterations between power updates. Increase this value if the board + * can't keep up with the processing demands of LASER_POWER_INLINE_TRAPEZOID_CONT. + * Disable (or set to 0) to recalculate power on every stepper iteration. + */ + //#define LASER_POWER_INLINE_TRAPEZOID_CONT_PER 10 + + /** + * Include laser power in G0/G1/G2/G3/G5 commands with the 'S' parameter + */ + //#define LASER_MOVE_POWER + + #if ENABLED(LASER_MOVE_POWER) + // Turn off the laser on G0 moves with no power parameter. + // If a power parameter is provided, use that instead. + //#define LASER_MOVE_G0_OFF + #endif + + /** + * Inline flag inverted + * + * WARNING: M5 will NOT turn off the laser unless another move + * is done (so G-code files must end with 'M5 I'). + */ + //#define LASER_POWER_INLINE_INVERT + + /** + * Continuously apply inline power. ('M3 S3' == 'G1 S3' == 'M3 S3 I') + * + * The laser might do some weird things, so only enable this + * feature if you understand the implications. + */ + //#define LASER_POWER_INLINE_CONTINUOUS + + #else + + #define SPINDLE_LASER_POWERUP_DELAY 50 // (ms) Delay to allow the spindle/laser to come up to speed/power + #define SPINDLE_LASER_POWERDOWN_DELAY 50 // (ms) Delay to allow the spindle to stop + + #endif #endif #endif diff --git a/Marlin/src/HAL/AVR/HAL.h b/Marlin/src/HAL/AVR/HAL.h index 0255169819..f715295d0a 100644 --- a/Marlin/src/HAL/AVR/HAL.h +++ b/Marlin/src/HAL/AVR/HAL.h @@ -395,6 +395,8 @@ inline void HAL_adc_init() { // AVR compatibility #define strtof strtod +#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment + /** * set_pwm_frequency * Sets the frequency of the timer corresponding to the provided pin diff --git a/Marlin/src/HAL/AVR/fast_pwm.cpp b/Marlin/src/HAL/AVR/fast_pwm.cpp index 42e7cc3f10..e855057486 100644 --- a/Marlin/src/HAL/AVR/fast_pwm.cpp +++ b/Marlin/src/HAL/AVR/fast_pwm.cpp @@ -23,7 +23,7 @@ #include "../../inc/MarlinConfigPre.h" -#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM +#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM #include "HAL.h" @@ -278,5 +278,5 @@ void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255 } } -#endif // FAST_PWM_FAN || SPINDLE_LASER_PWM +#endif // NEEDS_HARDWARE_PWM #endif // __AVR__ diff --git a/Marlin/src/HAL/LPC1768/HAL.h b/Marlin/src/HAL/LPC1768/HAL.h index f5ea629f16..70bb50e692 100644 --- a/Marlin/src/HAL/LPC1768/HAL.h +++ b/Marlin/src/HAL/LPC1768/HAL.h @@ -197,6 +197,8 @@ void HAL_idletask(); #define PLATFORM_M997_SUPPORT void flashFirmware(const int16_t); +#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment + /** * set_pwm_frequency * Set the frequency of the timer corresponding to the provided pin diff --git a/Marlin/src/HAL/LPC1768/fast_pwm.cpp b/Marlin/src/HAL/LPC1768/fast_pwm.cpp index a1feb25903..4f2e30ee72 100644 --- a/Marlin/src/HAL/LPC1768/fast_pwm.cpp +++ b/Marlin/src/HAL/LPC1768/fast_pwm.cpp @@ -24,7 +24,7 @@ #include "../../inc/MarlinConfigPre.h" -#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM +#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM #include diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 3a6582bbb1..5073c339cd 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -420,7 +420,11 @@ void startOrResumeJob() { #if DISABLED(SD_ABORT_NO_COOLDOWN) thermalManager.disable_all_heaters(); #endif - thermalManager.zero_fan_speeds(); + #if !HAS_CUTTER + thermalManager.zero_fan_speeds(); + #else + cutter.kill(); // Full cutter shutdown including ISR control + #endif wait_for_heatup = false; #if ENABLED(POWER_LOSS_RECOVERY) recovery.purge(); @@ -741,6 +745,10 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) { void kill(PGM_P const lcd_error/*=nullptr*/, PGM_P const lcd_component/*=nullptr*/, const bool steppers_off/*=false*/) { thermalManager.disable_all_heaters(); + #if HAS_CUTTER + cutter.kill(); // Full cutter shutdown including ISR control + #endif + SERIAL_ERROR_MSG(STR_ERR_KILLED); #if HAS_DISPLAY @@ -770,6 +778,10 @@ void minkill(const bool steppers_off/*=false*/) { // Reiterate heaters off thermalManager.disable_all_heaters(); + #if HAS_CUTTER + cutter.kill(); // Reiterate cutter shutdown + #endif + // Power off all steppers (for M112) or just the E steppers steppers_off ? disable_all_steppers() : disable_e_steppers(); @@ -789,14 +801,14 @@ void minkill(const bool steppers_off/*=false*/) { // Wait for kill to be pressed while (READ(KILL_PIN)) watchdog_refresh(); - void (*resetFunc)() = 0; // Declare resetFunc() at address 0 + void (*resetFunc)() = 0; // Declare resetFunc() at address 0 resetFunc(); // Jump to address 0 - #else // !HAS_KILL + #else - for (;;) watchdog_refresh(); // Wait for reset + for (;;) watchdog_refresh(); // Wait for reset - #endif // !HAS_KILL + #endif } /** diff --git a/Marlin/src/core/drivers.h b/Marlin/src/core/drivers.h index 833899bdcb..418a24ec92 100644 --- a/Marlin/src/core/drivers.h +++ b/Marlin/src/core/drivers.h @@ -174,15 +174,6 @@ // Defines that can't be evaluated now #define HAS_TMC_SW_SERIAL ANY_AXIS_HAS(SW_SERIAL) -// -// Stretching 'drivers.h' to include LPC/SAMD51 SD options -// -#define _SDCARD_LCD 1 -#define _SDCARD_ONBOARD 2 -#define _SDCARD_CUSTOM_CABLE 3 -#define _SDCARD_ID(V) _CAT(_SDCARD_, V) -#define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V)) - #if HAS_DRIVER(L6470) || HAS_DRIVER(L6474) || HAS_DRIVER(L6480) || HAS_DRIVER(POWERSTEP01) #define HAS_L64XX 1 #endif diff --git a/Marlin/src/feature/spindle_laser.cpp b/Marlin/src/feature/spindle_laser.cpp index 0567ba5d3e..63c9891109 100644 --- a/Marlin/src/feature/spindle_laser.cpp +++ b/Marlin/src/feature/spindle_laser.cpp @@ -32,10 +32,17 @@ SpindleLaser cutter; -cutter_power_t SpindleLaser::power; // = 0 - +cutter_power_t SpindleLaser::power; +bool SpindleLaser::isOn; // state to determine when to apply setPower to power +cutter_setPower_t SpindleLaser::setPower = interpret_power(SPEED_POWER_MIN); // spindle/laser speed/power control in PWM, Percentage or RPM +#if ENABLED(MARLIN_DEV_MODE) + cutter_frequency_t SpindleLaser::frequency; // setting PWM frequency; range: 2K - 50K +#endif #define SPINDLE_LASER_PWM_OFF ((SPINDLE_LASER_PWM_INVERT) ? 255 : 0) +// +// Init the cutter to a safe OFF state +// void SpindleLaser::init() { OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Init spindle to off #if ENABLED(SPINDLE_CHANGE_DIR) @@ -45,41 +52,39 @@ void SpindleLaser::init() { SET_PWM(SPINDLE_LASER_PWM_PIN); analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // set to lowest speed #endif + #if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY) + set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY); + #if ENABLED(MARLIN_DEV_MODE) + frequency = SPINDLE_LASER_FREQUENCY; + #endif + #endif } #if ENABLED(SPINDLE_LASER_PWM) /** - * ocr_val_mode() is used for debugging and to get the points needed to compute the RPM vs ocr_val line - * - * it accepts inputs of 0-255 - */ + * Set the cutter PWM directly to the given ocr value + **/ void SpindleLaser::set_ocr(const uint8_t ocr) { - WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on (active low) + WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF); } #endif +// +// Set cutter ON state (and PWM) to the given cutter power value +// void SpindleLaser::apply_power(const cutter_power_t inpow) { static cutter_power_t last_power_applied = 0; if (inpow == last_power_applied) return; last_power_applied = inpow; #if ENABLED(SPINDLE_LASER_PWM) - if (enabled()) { - #define _scaled(F) ((F - (SPEED_POWER_INTERCEPT)) * inv_slope) - constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE), - min_ocr = _scaled(SPEED_POWER_MIN), - max_ocr = _scaled(SPEED_POWER_MAX); - int16_t ocr_val; - if (inpow <= SPEED_POWER_MIN) ocr_val = min_ocr; // Use minimum if set below - else if (inpow >= SPEED_POWER_MAX) ocr_val = max_ocr; // Use maximum if set above - else ocr_val = _scaled(inpow); // Use calculated OCR value - set_ocr(ocr_val & 0xFF); // ...limited to Atmel PWM max - } + if (enabled()) + set_ocr(translate_power(inpow)); else { - WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Turn spindle off (active low) - analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Only write low byte + WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Turn spindle off + analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Only write low byte } #else WRITE(SPINDLE_LASER_ENA_PIN, (SPINDLE_LASER_ACTIVE_HIGH) ? enabled() : !enabled()); @@ -88,6 +93,10 @@ void SpindleLaser::apply_power(const cutter_power_t inpow) { #if ENABLED(SPINDLE_CHANGE_DIR) + // + // Set the spindle direction and apply immediately + // Stop on direction change if SPINDLE_STOP_ON_DIR_CHANGE is enabled + // void SpindleLaser::set_direction(const bool reverse) { const bool dir_state = (reverse == SPINDLE_INVERT_DIR); // Forward (M3) HIGH when not inverted #if ENABLED(SPINDLE_STOP_ON_DIR_CHANGE) diff --git a/Marlin/src/feature/spindle_laser.h b/Marlin/src/feature/spindle_laser.h index ea035299be..3d7ab6c360 100644 --- a/Marlin/src/feature/spindle_laser.h +++ b/Marlin/src/feature/spindle_laser.h @@ -28,55 +28,98 @@ #include "../inc/MarlinConfig.h" -#if ENABLED(SPINDLE_FEATURE) - #define _MSG_CUTTER(M) MSG_SPINDLE_##M -#else - #define _MSG_CUTTER(M) MSG_LASER_##M -#endif -#define MSG_CUTTER(M) _MSG_CUTTER(M) - -#if SPEED_POWER_MAX > 255 - typedef uint16_t cutter_power_t; - #define CUTTER_MENU_TYPE uint16_5 -#else - typedef uint8_t cutter_power_t; - #define CUTTER_MENU_TYPE uint8 +#include "spindle_laser_types.h" + +#if ENABLED(LASER_POWER_INLINE) + #include "../module/planner.h" #endif class SpindleLaser { public: + static bool isOn; // state to determine when to apply setPower to power static cutter_power_t power; - static inline uint8_t powerPercent(const uint8_t pp) { return ui8_to_percent(pp); } // for display - - static void init(); - - static inline bool enabled() { return !!power; } + static cutter_setPower_t setPower; // spindle/laser menu set power; in PWM, Percentage or RPM + #if ENABLED(MARLIN_DEV_MODE) + static cutter_frequency_t frequency; // set PWM frequency; range: 2K-50K + #endif - static inline void set_power(const cutter_power_t pwr) { power = pwr; } + static cutter_setPower_t interpret_power(const float pwr) { // convert speed/power to configured PWM, Percentage or RPM in relative or normal range + #if CUTTER_DISPLAY_IS(PERCENT) + return (pwr / SPEED_POWER_MAX) * 100; // to percent + #elif CUTTER_DISPLAY_IS(RPM) // to RPM is unaltered + return pwr; + #else // to PWM + #if ENABLED(CUTTER_POWER_RELATIVE) + return (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 255; // using rpm range as relative percentage + #else + return (pwr / SPEED_POWER_MAX) * 255; + #endif + #endif + } + /** + * Translate speed/power --> percentage --> PWM value + **/ + static cutter_power_t translate_power(const float pwr) { + float pwrpc; + #if CUTTER_DISPLAY_IS(PERCENT) + pwrpc = pwr; + #elif CUTTER_DISPLAY_IS(RPM) // RPM to percent + #if ENABLED(CUTTER_POWER_RELATIVE) + pwrpc = (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 100; + #else + pwrpc = pwr / SPEED_POWER_MAX * 100; + #endif + #else + return pwr; // PWM + #endif - static inline void refresh() { apply_power(power); } + #if ENABLED(SPINDLE_FEATURE) + #if ENABLED(CUTTER_POWER_RELATIVE) + constexpr float spmin = 0; + #else + constexpr float spmin = SPEED_POWER_MIN / SPEED_POWER_MAX * 100; // convert to percentage + #endif + constexpr float spmax = 100; + #else + constexpr float spmin = SPEED_POWER_MIN; + constexpr float spmax = SPEED_POWER_MAX; + #endif - static inline void set_enabled(const bool enable) { - const bool was = enabled(); - set_power(enable ? 255 : 0); - if (was != enable) power_delay(); + constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE), + min_ocr = (spmin - (SPEED_POWER_INTERCEPT)) * inv_slope, // Minimum allowed + max_ocr = (spmax - (SPEED_POWER_INTERCEPT)) * inv_slope; // Maximum allowed + float ocr_val; + if (pwrpc < spmin) ocr_val = min_ocr; // Use minimum if set below + else if (pwrpc > spmax) ocr_val = max_ocr; // Use maximum if set above + else ocr_val = (pwrpc - (SPEED_POWER_INTERCEPT)) * inv_slope; // Use calculated OCR value + return ocr_val; // ...limited to Atmel PWM max } + static void init(); + + // Modifying this function should update everywhere + static inline bool enabled(const cutter_power_t pwr) { return pwr > 0; } + static inline bool enabled() { return enabled(power); } + #if ENABLED(MARLIN_DEV_MODE) + static inline void refresh_frequency() { set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); } + #endif static void apply_power(const cutter_power_t inpow); - //static bool active() { return READ(SPINDLE_LASER_ENA_PIN) == SPINDLE_LASER_ACTIVE_HIGH; } + FORCE_INLINE static void refresh() { apply_power(power); } + FORCE_INLINE static void set_power(const cutter_power_t pwr) { power = pwr; refresh(); } - static void update_output(); + static inline void set_enabled(const bool enable) { set_power(enable ? (power ?: interpret_power(SPEED_POWER_STARTUP)) : 0); } #if ENABLED(SPINDLE_LASER_PWM) static void set_ocr(const uint8_t ocr); - static inline void set_ocr_power(const cutter_power_t pwr) { power = pwr; set_ocr(pwr); } + static inline void set_ocr_power(const uint8_t pwr) { power = pwr; set_ocr(pwr); } + // static uint8_t translate_power(const cutter_power_t pwr); // Used by update output for power->OCR translation #endif // Wait for spindle to spin up or spin down - static inline void power_delay() { - #if SPINDLE_LASER_POWERUP_DELAY || SPINDLE_LASER_POWERDOWN_DELAY - safe_delay(enabled() ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY); + static inline void power_delay(const bool on) { + #if DISABLED(LASER_POWER_INLINE) + safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY); #endif } @@ -86,10 +129,44 @@ public: static inline void set_direction(const bool) {} #endif - static inline void disable() { set_enabled(false); } - static inline void enable_forward() { set_direction(false); set_enabled(true); } - static inline void enable_reverse() { set_direction(true); set_enabled(true); } + static inline void disable() { isOn = false; set_enabled(false); } + #if HAS_LCD_MENU + static inline void enable_forward() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(false); set_enabled(true); } + static inline void enable_reverse() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(true); set_enabled(true); } + #endif + #if ENABLED(LASER_POWER_INLINE) + // Force disengage planner power control + static inline void inline_disable() { planner.settings.laser.status = 0; planner.settings.laser.power = 0; isOn = false;} + + // Inline modes of all other functions; all enable planner inline power control + static inline void inline_enabled(const bool enable) { enable ? inline_power(SPEED_POWER_STARTUP) : inline_ocr_power(0); } + + static void inline_power(const cutter_power_t pwr) { + #if ENABLED(SPINDLE_LASER_PWM) + inline_ocr_power(translate_power(pwr)); + #else + planner.settings.laser.status = enabled(pwr) ? 0x03 : 0x01; + planner.settings.laser.power = pwr; + #endif + } + + static inline void inline_direction(const bool reverse) { UNUSED(reverse); } // TODO is this ever going to be needed + + #if ENABLED(SPINDLE_LASER_PWM) + static inline void inline_ocr_power(const uint8_t pwr) { + planner.settings.laser.status = pwr ? 0x03 : 0x01; + planner.settings.laser.power = pwr; + } + #endif + #endif + + static inline void kill() { + #if ENABLED(LASER_POWER_INLINE) + inline_disable(); + #endif + disable(); + } }; extern SpindleLaser cutter; diff --git a/Marlin/src/feature/spindle_laser_types.h b/Marlin/src/feature/spindle_laser_types.h new file mode 100644 index 0000000000..9e3f2bae48 --- /dev/null +++ b/Marlin/src/feature/spindle_laser_types.h @@ -0,0 +1,50 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2019 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 . + * + */ +#pragma once + +/** + * feature/spindle_laser_types.h + * Support for Laser Power or Spindle Power & Direction + */ + +#include "../inc/MarlinConfigPre.h" + +#if ENABLED(SPINDLE_FEATURE) + #define _MSG_CUTTER(M) MSG_SPINDLE_##M +#else + #define _MSG_CUTTER(M) MSG_LASER_##M +#endif +#define MSG_CUTTER(M) _MSG_CUTTER(M) +#if CUTTER_DISPLAY_IS(RPM) && SPEED_POWER_MAX > 255 + #define cutter_power_t uint16_t + #define cutter_setPower_t uint16_t + #define CUTTER_MENU_POWER_TYPE uint16_5 +#else + #define cutter_power_t uint8_t + #define cutter_setPower_t uint8_t + #define CUTTER_MENU_POWER_TYPE uint8 +#endif + +#if ENABLED(MARLIN_DEV_MODE) + #define cutter_frequency_t uint16_t + #define CUTTER_MENU_FREQUENCY_TYPE uint16_5 +#endif diff --git a/Marlin/src/gcode/control/M3-M5.cpp b/Marlin/src/gcode/control/M3-M5.cpp index 56e1e0e4ec..9c897abf01 100644 --- a/Marlin/src/gcode/control/M3-M5.cpp +++ b/Marlin/src/gcode/control/M3-M5.cpp @@ -28,6 +28,12 @@ #include "../../feature/spindle_laser.h" #include "../../module/stepper.h" +inline cutter_power_t get_s_power() { + return cutter_power_t( + parser.intval('S', cutter.interpret_power(SPEED_POWER_STARTUP)) + ); +} + /** * Laser: * @@ -71,29 +77,52 @@ */ void GcodeSuite::M3_M4(const bool is_M4) { - #if ENABLED(SPINDLE_FEATURE) - planner.synchronize(); // Wait for movement to complete before changing power + #if ENABLED(LASER_POWER_INLINE) + if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) { + // Laser power in inline mode + cutter.inline_direction(is_M4); // Should always be unused + + #if ENABLED(SPINDLE_LASER_PWM) + if (parser.seen('O')) + cutter.inline_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t) + else + cutter.inline_power(get_s_power()); + #else + cutter.inline_enabled(true); + #endif + return; + } + // Non-inline, standard case + cutter.inline_disable(); // Prevent future blocks re-setting the power #endif + planner.synchronize(); // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power + cutter.set_direction(is_M4); #if ENABLED(SPINDLE_LASER_PWM) if (parser.seenval('O')) cutter.set_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t) else - cutter.set_power(parser.intval('S', 255)); + cutter.set_power(get_s_power()); #else cutter.set_enabled(true); #endif } /** - * M5 - Cutter OFF + * M5 - Cutter OFF (when moves are complete) */ void GcodeSuite::M5() { - #if ENABLED(SPINDLE_FEATURE) - planner.synchronize(); + #if ENABLED(LASER_POWER_INLINE) + if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) { + cutter.inline_enabled(false); // Laser power in inline mode + return; + } + // Non-inline, standard case + cutter.inline_disable(); // Prevent future blocks re-setting the power #endif + planner.synchronize(); cutter.set_enabled(false); } diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index 1f9d710bb4..0937a86bd2 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -53,6 +53,10 @@ GcodeSuite gcode; #include "../feature/cancel_object.h" #endif +#if ENABLED(LASER_MOVE_POWER) + #include "../feature/spindle_laser.h" +#endif + #include "../MarlinCore.h" // for idle() millis_t GcodeSuite::previous_move_ms; @@ -172,6 +176,18 @@ void GcodeSuite::get_destination_from_command() { #if BOTH(MIXING_EXTRUDER, DIRECT_MIXING_IN_G1) M165(); #endif + + #if ENABLED(LASER_MOVE_POWER) + // Set the laser power in the planner to configure this move + if (parser.seen('S')) + cutter.inline_power(parser.value_int()); + else { + #if ENABLED(LASER_MOVE_G0_OFF) + if (parser.codenum == 0) // G0 + cutter.inline_enabled(false); + #endif + } + #endif } /** diff --git a/Marlin/src/gcode/motion/G0_G1.cpp b/Marlin/src/gcode/motion/G0_G1.cpp index 069d76fdad..df0825a512 100644 --- a/Marlin/src/gcode/motion/G0_G1.cpp +++ b/Marlin/src/gcode/motion/G0_G1.cpp @@ -69,7 +69,7 @@ void GcodeSuite::G0_G1( #endif #endif - get_destination_from_command(); // Process X Y Z E F parameters + get_destination_from_command(); // Get X Y Z E F (and set cutter power) #ifdef G0_FEEDRATE if (fast_move) { diff --git a/Marlin/src/gcode/motion/G2_G3.cpp b/Marlin/src/gcode/motion/G2_G3.cpp index b0fb299ab2..d6b18bdd95 100644 --- a/Marlin/src/gcode/motion/G2_G3.cpp +++ b/Marlin/src/gcode/motion/G2_G3.cpp @@ -283,7 +283,7 @@ void GcodeSuite::G2_G3(const bool clockwise) { relative_mode = true; #endif - get_destination_from_command(); + get_destination_from_command(); // Get X Y Z E F (and set cutter power) #if ENABLED(SF_ARC_FIX) relative_mode = relative_mode_backup; diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h index a52ae1af29..cda744d81b 100644 --- a/Marlin/src/inc/Conditionals_adv.h +++ b/Marlin/src/inc/Conditionals_adv.h @@ -116,7 +116,23 @@ #define Z_STEPPER_ALIGN_AMP 1.0 #endif -#define HAS_CUTTER EITHER(SPINDLE_FEATURE, LASER_FEATURE) +// +// Spindle/Laser power display types +// Defined here so sanity checks can use them +// +#if EITHER(SPINDLE_FEATURE, LASER_FEATURE) + #define HAS_CUTTER 1 + #define _CUTTER_DISP_PWM 1 + #define _CUTTER_DISP_PERCENT 2 + #define _CUTTER_DISP_RPM 3 + #define _CUTTER_DISP(V) _CAT(_CUTTER_DISP_, V) + #define CUTTER_DISPLAY_IS(V) (_CUTTER_DISP(CUTTER_POWER_DISPLAY) == _CUTTER_DISP(V)) +#endif + +// Add features that need hardware PWM here +#if ANY(FAST_PWM_FAN, SPINDLE_LASER_PWM) + #define NEEDS_HARDWARE_PWM 1 +#endif #if !defined(__AVR__) || !defined(USBCON) // Define constants and variables for buffering serial data. @@ -290,3 +306,17 @@ #define MAXIMUM_STEPPER_RATE 250000 #endif #endif + +// +// SD Card connection methods +// Defined here so pins and sanity checks can use them +// +#if ENABLED(SDSUPPORT) + #define _SDCARD_LCD 1 + #define _SDCARD_ONBOARD 2 + #define _SDCARD_CUSTOM_CABLE 3 + #define _SDCARD_ID(V) _CAT(_SDCARD_, V) + #define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V)) +#else + #define SD_CONNECTION_IS(...) 0 +#endif diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h index 20867b4fea..b38aaa1332 100644 --- a/Marlin/src/inc/Conditionals_post.h +++ b/Marlin/src/inc/Conditionals_post.h @@ -324,17 +324,36 @@ /** * Override the SD_DETECT_STATE set in Configuration_adv.h + * and enable sharing of onboard SD host drives (all platforms but AGCM4) */ #if ENABLED(SDSUPPORT) - #if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION)) - #undef SD_DETECT_STATE - #if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) - #define SD_DETECT_STATE HIGH - #endif + + #if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4) + // + // The external SD card is not used. Hardware SPI is used to access the card. + // When sharing the SD card with a PC we want the menu options to + // mount/unmount the card and refresh it. So we disable card detect. + // + #undef SD_DETECT_PIN + #define SHARED_SD_CARD + #endif + + #if DISABLED(SHARED_SD_CARD) + #define INIT_SDCARD_ON_BOOT 1 #endif - #ifndef SD_DETECT_STATE - #define SD_DETECT_STATE LOW + + #if PIN_EXISTS(SD_DETECT) + #if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION)) + #undef SD_DETECT_STATE + #if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) + #define SD_DETECT_STATE HIGH + #endif + #endif + #ifndef SD_DETECT_STATE + #define SD_DETECT_STATE LOW + #endif #endif + #endif /** @@ -2153,21 +2172,6 @@ #endif #endif -#if ENABLED(SDSUPPORT) - #if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4) - // - // The external SD card is not used. Hardware SPI is used to access the card. - // When sharing the SD card with a PC we want the menu options to - // mount/unmount the card and refresh it. So we disable card detect. - // - #undef SD_DETECT_PIN - #define SHARED_SD_CARD - #endif - #if DISABLED(SHARED_SD_CARD) - #define INIT_SDCARD_ON_BOOT 1 - #endif -#endif - #if !NUM_SERIAL #undef BAUD_RATE_GCODE #endif diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 2fd61d4be1..088ad098db 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -1451,7 +1451,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS * Deploying the Allen Key probe uses big moves in z direction. Too dangerous for an unhomed z-axis. */ #if ENABLED(Z_PROBE_ALLEN_KEY) && (Z_HOME_DIR < 0) && ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) - #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY" + #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY." #endif /** @@ -2654,9 +2654,9 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) #if ENABLED(BACKLASH_COMPENSATION) #ifndef BACKLASH_DISTANCE_MM - #error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM" + #error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM." #elif !defined(BACKLASH_CORRECTION) - #error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION" + #error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION." #elif IS_CORE constexpr float backlash_arr[] = BACKLASH_DISTANCE_MM; static_assert(!backlash_arr[CORE_AXIS_1] && !backlash_arr[CORE_AXIS_2], @@ -2736,6 +2736,45 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) #endif #if HAS_CUTTER + #ifndef CUTTER_POWER_DISPLAY + #error "CUTTER_POWER_DISPLAY is required with a spindle or laser. Please update your Configuration_adv.h." + #elif !CUTTER_DISPLAY_IS(PWM) && !CUTTER_DISPLAY_IS(PERCENT) && !CUTTER_DISPLAY_IS(RPM) + #error "CUTTER_POWER_DISPLAY must be PWM, PERCENT, or RPM. Please update your Configuration_adv.h." + #endif + + #if ENABLED(LASER_POWER_INLINE) + #if ENABLED(SPINDLE_CHANGE_DIR) + #error "SPINDLE_CHANGE_DIR and LASER_POWER_INLINE are incompatible." + #elif ENABLED(LASER_MOVE_G0_OFF) && DISABLED(LASER_MOVE_POWER) + #error "LASER_MOVE_G0_OFF requires LASER_MOVE_POWER. Please update your Configuration_adv.h." + #endif + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + #if DISABLED(SPINDLE_LASER_PWM) + #error "LASER_POWER_INLINE_TRAPEZOID requires SPINDLE_LASER_PWM to function." + #elif ENABLED(S_CURVE_ACCELERATION) + //#ifndef LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN + // #define LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN + // #warning "Combining LASER_POWER_INLINE_TRAPEZOID with S_CURVE_ACCELERATION may result in unintended behavior." + //#endif + #endif + #endif + #if ENABLED(LASER_POWER_INLINE_INVERT) + //#ifndef LASER_POWER_INLINE_INVERT_WARN + // #define LASER_POWER_INLINE_INVERT_WARN + // #warning "Enabling LASER_POWER_INLINE_INVERT means that `M5` won't kill the laser immediately; use `M5 I` instead." + //#endif + #endif + #else + #if SPINDLE_LASER_POWERUP_DELAY < 1 + #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0." + #elif SPINDLE_LASER_POWERDOWN_DELAY < 1 + #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0." + #elif ENABLED(LASER_MOVE_POWER) + #error "LASER_MOVE_POWER requires LASER_POWER_INLINE." + #elif ANY(LASER_POWER_INLINE_TRAPEZOID, LASER_POWER_INLINE_INVERT, LASER_MOVE_G0_OFF, LASER_MOVE_POWER) + #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." @@ -2748,13 +2787,9 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) #error "SPINDLE_LASER_PWM_PIN is required for SPINDLE_LASER_PWM." #elif !PWM_PIN(SPINDLE_LASER_PWM_PIN) #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin." - #elif SPINDLE_LASER_POWERUP_DELAY < 1 - #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0." - #elif SPINDLE_LASER_POWERDOWN_DELAY < 1 - #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0." #elif !defined(SPINDLE_LASER_PWM_INVERT) #error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE." - #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX) + #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX) || !defined(SPEED_POWER_STARTUP) #error "SPINDLE_LASER_PWM equation constant(s) missing." #elif _PIN_CONFLICT(X_MIN) #error "SPINDLE_LASER_PWM pin conflicts with X_MIN_PIN." diff --git a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp index be414ab303..b4bba3782c 100644 --- a/Marlin/src/lcd/dogm/status_screen_DOGM.cpp +++ b/Marlin/src/lcd/dogm/status_screen_DOGM.cpp @@ -570,8 +570,12 @@ void MarlinUI::draw_status_screen() { // Laser / Spindle #if DO_DRAW_CUTTER if (cutter.power && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) { - lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.powerPercent(cutter.power))); - lcd_put_wchar('%'); + lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.power)); + #if CUTTER_DISPLAY_IS(PERCENT) + lcd_put_wchar('%'); + #elif CUTTER_DISPLAY_IS(RPM) + lcd_put_wchar('K'); + #endif } #endif diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h index 79a6f814b2..0aea571caa 100644 --- a/Marlin/src/lcd/language/language_en.h +++ b/Marlin/src/lcd/language/language_en.h @@ -90,6 +90,7 @@ namespace Language_en { PROGMEM Language_Str MSG_PREHEAT_2_SETTINGS = _UxGT("Preheat ") PREHEAT_2_LABEL _UxGT(" Conf"); PROGMEM Language_Str MSG_PREHEAT_CUSTOM = _UxGT("Preheat Custom"); PROGMEM Language_Str MSG_COOLDOWN = _UxGT("Cooldown"); + PROGMEM Language_Str MSG_CUTTER_FREQUENCY = _UxGT("Frequency"); PROGMEM Language_Str MSG_LASER_MENU = _UxGT("Laser Control"); PROGMEM Language_Str MSG_LASER_OFF = _UxGT("Laser Off"); PROGMEM Language_Str MSG_LASER_ON = _UxGT("Laser On"); @@ -603,7 +604,7 @@ namespace Language_en { PROGMEM Language_Str MSG_BACKLASH_C = LCD_STR_C; PROGMEM Language_Str MSG_BACKLASH_CORRECTION = _UxGT("Correction"); PROGMEM Language_Str MSG_BACKLASH_SMOOTHING = _UxGT("Smoothing"); - + PROGMEM Language_Str MSG_LEVEL_X_AXIS = _UxGT("Level X Axis"); PROGMEM Language_Str MSG_AUTO_CALIBRATE = _UxGT("Auto Calibrate"); PROGMEM Language_Str MSG_HEATER_TIMEOUT = _UxGT("Heater Timeout"); diff --git a/Marlin/src/lcd/menu/menu_spindle_laser.cpp b/Marlin/src/lcd/menu/menu_spindle_laser.cpp index fd42522839..d8e680ec3c 100644 --- a/Marlin/src/lcd/menu/menu_spindle_laser.cpp +++ b/Marlin/src/lcd/menu/menu_spindle_laser.cpp @@ -36,18 +36,29 @@ START_MENU(); BACK_ITEM(MSG_MAIN); - if (cutter.enabled()) { - #if ENABLED(SPINDLE_LASER_PWM) - EDIT_ITEM(CUTTER_MENU_TYPE, MSG_CUTTER(POWER), &cutter.power, SPEED_POWER_MIN, SPEED_POWER_MAX); - #endif + #if ENABLED(SPINDLE_LASER_PWM) + EDIT_ITEM_FAST(CUTTER_MENU_POWER_TYPE, MSG_CUTTER(POWER), &cutter.setPower, cutter.interpret_power(SPEED_POWER_MIN), cutter.interpret_power(SPEED_POWER_MAX), + []{ + if (cutter.isOn) { + cutter.power = cutter.setPower; + } + }); + #endif + + if (cutter.enabled() && cutter.isOn) ACTION_ITEM(MSG_CUTTER(OFF), cutter.disable); - } else { ACTION_ITEM(MSG_CUTTER(ON), cutter.enable_forward); #if ENABLED(SPINDLE_CHANGE_DIR) ACTION_ITEM(MSG_SPINDLE_REVERSE, cutter.enable_reverse); #endif } + + #if ENABLED(MARLIN_DEV_MODE) + #if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY) + EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 50000,[]{ cutter.refresh_frequency();}); + #endif + #endif END_MENU(); } diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp index e1a050a459..2de46373b2 100644 --- a/Marlin/src/module/planner.cpp +++ b/Marlin/src/module/planner.cpp @@ -815,11 +815,10 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e #if ENABLED(S_CURVE_ACCELERATION) // Jerk controlled speed requires to express speed versus time, NOT steps uint32_t acceleration_time = ((float)(cruise_rate - initial_rate) / accel) * (STEPPER_TIMER_RATE), - deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE); - + deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE), // And to offload calculations from the ISR, we also calculate the inverse of those times here - uint32_t acceleration_time_inverse = get_period_inverse(acceleration_time); - uint32_t deceleration_time_inverse = get_period_inverse(deceleration_time); + acceleration_time_inverse = get_period_inverse(acceleration_time), + deceleration_time_inverse = get_period_inverse(deceleration_time); #endif // Store new block parameters @@ -834,6 +833,47 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e block->cruise_rate = cruise_rate; #endif block->final_rate = final_rate; + + /** + * Laser trapezoid calculations + * + * Approximate the trapezoid with the laser, incrementing the power every `entry_per` while accelerating + * and decrementing it every `exit_power_per` while decelerating, thus ensuring power is related to feedrate. + * + * LASER_POWER_INLINE_TRAPEZOID_CONT doesn't need this as it continuously approximates + * + * Note this may behave unreliably when running with S_CURVE_ACCELERATION + */ + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + if (block->laser.power > 0) { // No need to care if power == 0 + const uint8_t entry_power = block->laser.power * entry_factor; // Power on block entry + #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + // Speedup power + const uint8_t entry_power_diff = block->laser.power - entry_power; + if (entry_power_diff) { + block->laser.entry_per = accelerate_steps / entry_power_diff; + block->laser.power_entry = entry_power; + } + else { + block->laser.entry_per = 0; + block->laser.power_entry = block->laser.power; + } + // Slowdown power + const uint8_t exit_power = block->laser.power * exit_factor, // Power on block entry + exit_power_diff = block->laser.power - exit_power; + if (exit_power_diff) { + block->laser.exit_per = (block->step_event_count - block->decelerate_after) / exit_power_diff; + block->laser.power_exit = exit_power; + } + else { + block->laser.exit_per = 0; + block->laser.power_exit = block->laser.power; + } + #else + block->laser.power_entry = entry_power; + #endif + } + #endif } /* PLANNER SPEED DEFINITION @@ -1813,6 +1853,12 @@ bool Planner::_populate_block(block_t * const block, bool split_move, // Set direction bits block->direction_bits = dm; + // Update block laser power + #if ENABLED(LASER_POWER_INLINE) + block->laser.status = settings.laser.status; + block->laser.power = settings.laser.power; + #endif + // Number of steps for each axis // See http://www.corexy.com/theory.html #if CORE_IS_XY diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h index 24c02c01c5..a42c84c1fa 100644 --- a/Marlin/src/module/planner.h +++ b/Marlin/src/module/planner.h @@ -52,7 +52,7 @@ #endif #if HAS_CUTTER - #include "../feature/spindle_laser.h" + #include "../feature/spindle_laser_types.h" #endif // Feedrate for manual moves @@ -88,6 +88,23 @@ enum BlockFlag : char { BLOCK_FLAG_SYNC_POSITION = _BV(BLOCK_BIT_SYNC_POSITION) }; +#if ENABLED(LASER_POWER_INLINE) + + typedef struct { + uint8_t status, // See planner settings for meaning + power; // Ditto; When in trapezoid mode this is nominal power + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + uint8_t power_entry; // Entry power for the laser + #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + uint8_t power_exit; // Exit power for the laser + uint32_t entry_per, // Steps per power increment (to avoid floats in stepper calcs) + exit_per; // Steps per power decrement + #endif + #endif + } block_laser_t; + +#endif + /** * struct block_t * @@ -174,12 +191,36 @@ typedef struct block_t { uint32_t sdpos; #endif + #if ENABLED(LASER_POWER_INLINE) + block_laser_t laser; + #endif + } block_t; #define HAS_POSITION_FLOAT ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL) #define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1)) +#if ENABLED(LASER_POWER_INLINE) + typedef struct { + /** + * Laser status bitmask; most bits are unused; + * 0: Planner buffer enable + * 1: Laser enable + * 2: Reserved for direction + */ + uint8_t status; + /** + * Laser power: 0 or 255 in case of PWM-less laser, + * or the OCR value; + * + * Using OCR instead of raw power, + * as it avoids floating points during move loop + */ + uint8_t power; + } settings_laser_t; +#endif + typedef struct { uint32_t max_acceleration_mm_per_s2[XYZE_N], // (mm/s^2) M201 XYZE min_segment_time_us; // (µs) M205 B @@ -190,6 +231,9 @@ typedef struct { travel_acceleration; // (mm/s^2) M204 T - Travel acceleration. DEFAULT ACCELERATION for all NON printing moves. feedRate_t min_feedrate_mm_s, // (mm/s) M205 S - Minimum linear feedrate min_travel_feedrate_mm_s; // (mm/s) M205 T - Minimum travel feedrate + #if ENABLED(LASER_POWER_INLINE) + settings_laser_t laser; + #endif } planner_settings_t; #if DISABLED(SKEW_CORRECTION) diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp index ed48b83d5a..f77a596b08 100644 --- a/Marlin/src/module/stepper.cpp +++ b/Marlin/src/module/stepper.cpp @@ -133,6 +133,10 @@ Stepper stepper; // Singleton #include "../feature/powerloss.h" #endif +#if HAS_CUTTER + #include "../feature/spindle_laser.h" +#endif + // public: #if HAS_EXTRA_ENDSTOPS || ENABLED(Z_STEPPER_AUTO_ALIGN) @@ -236,6 +240,20 @@ xyz_long_t Stepper::endstops_trigsteps; xyze_long_t Stepper::count_position{0}; xyze_int8_t Stepper::count_direction{0}; +#if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + Stepper::stepper_laser_t Stepper::laser = { + .trap_en = false, + .cur_power = 0, + .cruise_set = false, + #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + .last_step_count = 0, + .acc_step_count = 0 + #else + .till_update = 0 + #endif + }; +#endif + #define DUAL_ENDSTOP_APPLY_STEP(A,V) \ if (separate_multi_axis) { \ if (A##_HOME_DIR < 0) { \ @@ -1674,10 +1692,9 @@ uint32_t Stepper::block_phase_isr() { #if ENABLED(S_CURVE_ACCELERATION) // Get the next speed to use (Jerk limited!) - uint32_t acc_step_rate = - acceleration_time < current_block->acceleration_time - ? _eval_bezier_curve(acceleration_time) - : current_block->cruise_rate; + uint32_t acc_step_rate = acceleration_time < current_block->acceleration_time + ? _eval_bezier_curve(acceleration_time) + : current_block->cruise_rate; #else acc_step_rate = STEP_MULTIPLY(acceleration_time, current_block->acceleration_rate) + current_block->initial_rate; NOMORE(acc_step_rate, current_block->nominal_rate); @@ -1690,9 +1707,40 @@ uint32_t Stepper::block_phase_isr() { acceleration_time += interval; #if ENABLED(LIN_ADVANCE) - // Fire ISR if final adv_rate is reached - if (LA_steps && (!LA_use_advance_lead || LA_isr_rate != current_block->advance_speed)) - initiateLA(); + if (LA_use_advance_lead) { + // Fire ISR if final adv_rate is reached + if (LA_steps && LA_isr_rate != current_block->advance_speed) nextAdvanceISR = 0; + } + else if (LA_steps) nextAdvanceISR = 0; + #endif + + // Update laser - Accelerating + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + if (laser.trap_en) { + #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + if (current_block->laser.entry_per) { + laser.acc_step_count -= step_events_completed - laser.last_step_count; + laser.last_step_count = step_events_completed; + + // Should be faster than a divide, since this should trip just once + if (laser.acc_step_count < 0) { + while (laser.acc_step_count < 0) { + laser.acc_step_count += current_block->laser.entry_per; + if (laser.cur_power < current_block->laser.power) laser.cur_power++; + } + cutter.set_ocr_power(laser.cur_power); + } + } + #else + if (laser.till_update) + laser.till_update--; + else { + laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER; + laser.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate; + cutter.set_ocr_power(laser.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles + } + #endif + } #endif } // Are we in Deceleration phase ? @@ -1740,10 +1788,39 @@ uint32_t Stepper::block_phase_isr() { LA_isr_rate = current_block->advance_speed; } } - else if (LA_steps) initiateLA(); + else if (LA_steps) nextAdvanceISR = 0; + #endif // LIN_ADVANCE + + // Update laser - Decelerating + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + if (laser.trap_en) { + #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + if (current_block->laser.exit_per) { + laser.acc_step_count -= step_events_completed - laser.last_step_count; + laser.last_step_count = step_events_completed; + + // Should be faster than a divide, since this should trip just once + if (laser.acc_step_count < 0) { + while (laser.acc_step_count < 0) { + laser.acc_step_count += current_block->laser.exit_per; + if (laser.cur_power > current_block->laser.power_exit) laser.cur_power--; + } + cutter.set_ocr_power(laser.cur_power); + } + } + #else + if (laser.till_update) + laser.till_update--; + else { + laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER; + laser.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate; + cutter.set_ocr_power(laser.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles + } + #endif + } #endif } - // We must be in cruise phase otherwise + // Must be in cruise phase otherwise else { #if ENABLED(LIN_ADVANCE) @@ -1759,6 +1836,22 @@ uint32_t Stepper::block_phase_isr() { // The timer interval is just the nominal value for the nominal speed interval = ticks_nominal; + + // Update laser - Cruising + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + if (laser.trap_en) { + if (!laser.cruise_set) { + laser.cur_power = current_block->laser.power; + cutter.set_ocr_power(laser.cur_power); + laser.cruise_set = true; + } + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER; + #else + laser.last_step_count = step_events_completed; + #endif + } + #endif } } } @@ -1805,11 +1898,11 @@ uint32_t Stepper::block_phase_isr() { * If DeltaA == DeltaB, the movement is only in the 1st axis (X) */ #if EITHER(COREXY, COREXZ) - #define X_CMP == + #define X_CMP(A,B) ((A)==(B)) #else - #define X_CMP != + #define X_CMP(A,B) ((A)!=(B)) #endif - #define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) X_CMP D_(2)) ) + #define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && X_CMP(D_(1),D_(2))) ) #else #define X_MOVE_TEST !!current_block->steps.a #endif @@ -1823,11 +1916,11 @@ uint32_t Stepper::block_phase_isr() { * If DeltaA == -DeltaB, the movement is only in the 2nd axis (Y or Z) */ #if EITHER(COREYX, COREYZ) - #define Y_CMP == + #define Y_CMP(A,B) ((A)==(B)) #else - #define Y_CMP != + #define Y_CMP(A,B) ((A)!=(B)) #endif - #define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Y_CMP D_(2)) ) + #define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Y_CMP(D_(1),D_(2))) ) #else #define Y_MOVE_TEST !!current_block->steps.b #endif @@ -1841,11 +1934,11 @@ uint32_t Stepper::block_phase_isr() { * If DeltaA == -DeltaB, the movement is only in the 2nd axis (Z) */ #if EITHER(COREZX, COREZY) - #define Z_CMP == + #define Z_CMP(A,B) ((A)==(B)) #else - #define Z_CMP != + #define Z_CMP(A,B) ((A)!=(B)) #endif - #define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Z_CMP D_(2)) ) + #define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Z_CMP(D_(1),D_(2))) ) #else #define Z_MOVE_TEST !!current_block->steps.c #endif @@ -1938,6 +2031,39 @@ uint32_t Stepper::block_phase_isr() { set_directions(); } + #if ENABLED(LASER_POWER_INLINE) + const uint8_t stat = current_block->laser.status; + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + laser.trap_en = (stat & 0x03) == 0x03; + laser.cur_power = current_block->laser.power_entry; // RESET STATE + laser.cruise_set = false; + #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + laser.last_step_count = 0; + laser.acc_step_count = current_block->laser.entry_per / 2; + #else + laser.till_update = 0; + #endif + // Always have PWM in this case + if (TEST(stat, 0)) { // Planner controls the laser + if (TEST(stat, 1)) // Laser is on + cutter.set_ocr_power(laser.cur_power); + else + cutter.set_power(0); + } + #else + if (TEST(stat, 0)) { // Planner controls the laser + #if ENABLED(SPINDLE_LASER_PWM) + if (TEST(stat, 1)) // Laser is on + cutter.set_ocr_power(current_block->laser.power); + else + cutter.set_power(0); + #else + cutter.set_enabled(TEST(stat, 1)); + #endif + } + #endif + #endif // LASER_POWER_INLINE + // At this point, we must ensure the movement about to execute isn't // trying to force the head against a limit switch. If using interrupt- // driven change detection, and already against a limit then no call to @@ -1957,21 +2083,35 @@ uint32_t Stepper::block_phase_isr() { // Mark the time_nominal as not calculated yet ticks_nominal = -1; - #if DISABLED(S_CURVE_ACCELERATION) - // Set as deceleration point the initial rate of the block - acc_step_rate = current_block->initial_rate; - #endif - #if ENABLED(S_CURVE_ACCELERATION) // Initialize the Bézier speed curve _calc_bezier_curve_coeffs(current_block->initial_rate, current_block->cruise_rate, current_block->acceleration_time_inverse); // We haven't started the 2nd half of the trapezoid bezier_2nd_half = false; + #else + // Set as deceleration point the initial rate of the block + acc_step_rate = current_block->initial_rate; #endif // Calculate the initial timer interval interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr); } + #if ENABLED(LASER_POWER_INLINE_CONTINUOUS) + else { // No new block found; so apply inline laser parameters + // This should mean ending file with 'M5 I' will stop the laser; thus the inline flag isn't needed + const uint8_t stat = planner.settings.laser.status; + if (TEST(stat, 0)) { // Planner controls the laser + #if ENABLED(SPINDLE_LASER_PWM) + if (TEST(stat, 1)) // Laser is on + cutter.set_ocr_power(planner.settings.laser.power); + else + cutter.set_power(0); + #else + cutter.set_enabled(TEST(stat, 1)); + #endif + } + } + #endif } // Return the interval to wait diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h index 46c6c1c16a..3876980ad0 100644 --- a/Marlin/src/module/stepper.h +++ b/Marlin/src/module/stepper.h @@ -339,23 +339,35 @@ class Stepper { static uint32_t acc_step_rate; // needed for deceleration start point #endif - // // Exact steps at which an endstop was triggered - // static xyz_long_t endstops_trigsteps; - // // Positions of stepper motors, in step units - // static xyze_long_t count_position; - // - // Current direction of stepper motors (+1 or -1) - // + // Current stepper motor directions (+1 or -1) static xyze_int8_t count_direction; - public: + #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) + + typedef struct { + bool trap_en; // Trapezoid needed flag (i.e., laser on, planner in control) + uint8_t cur_power; // Current laser power + bool cruise_set; // Power set up for cruising? + + #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) + uint32_t last_step_count, // Step count from the last update + acc_step_count; // Bresenham counter for laser accel/decel + #else + uint16_t till_update; // Countdown to the next update + #endif + } stepper_laser_t; + static stepper_laser_t laser; + + #endif + + public: // Initialize stepper hardware static void init();