Various Laser / Spindle improvements (#15335)
This commit is contained in:
		| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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__ | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 <pwm.h> | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
| #include "spindle_laser_types.h" | ||||
|  | ||||
| #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 | ||||
| #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 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 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 | ||||
|  | ||||
|     #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 | ||||
|  | ||||
|     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(); | ||||
|  | ||||
|   static inline bool enabled() { return !!power; } | ||||
|  | ||||
|   static inline void set_power(const cutter_power_t pwr) { power = pwr; } | ||||
|  | ||||
|   static inline void refresh() { apply_power(power); } | ||||
|  | ||||
|   static inline void set_enabled(const bool enable) { | ||||
|     const bool was = enabled(); | ||||
|     set_power(enable ? 255 : 0); | ||||
|     if (was != enable) power_delay(); | ||||
|   } | ||||
|  | ||||
|   // 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; | ||||
|   | ||||
							
								
								
									
										50
									
								
								Marlin/src/feature/spindle_laser_types.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								Marlin/src/feature/spindle_laser_types.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 <http://www.gnu.org/licenses/>. | ||||
|  * | ||||
|  */ | ||||
| #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 | ||||
| @@ -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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   #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 | ||||
|  | ||||
|   #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 | ||||
|   #ifndef SD_DETECT_STATE | ||||
|     #define SD_DETECT_STATE LOW | ||||
|   #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 | ||||
|   | ||||
| @@ -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." | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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"); | ||||
|   | ||||
| @@ -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(); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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(); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user