Improvements for Laser / Spindle (#17661)
This commit is contained in:
		| @@ -2777,7 +2777,7 @@ | ||||
| #if EITHER(SPINDLE_FEATURE, LASER_FEATURE) | ||||
|   #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_PWM_INVERT      false  // Set to "true" if the speed/power goes up when you want it to go slower | ||||
|  | ||||
|   #define SPINDLE_LASER_FREQUENCY       2500   // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC) | ||||
|  | ||||
| @@ -2787,13 +2787,17 @@ | ||||
|    *  - PERCENT (S0 - S100) | ||||
|    *  - RPM     (S0 - S50000)  Best for use with a spindle | ||||
|    */ | ||||
|   #define CUTTER_POWER_DISPLAY PWM255 | ||||
|   #define CUTTER_POWER_UNIT PWM255 | ||||
|  | ||||
|   /** | ||||
|    * 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 | ||||
|    * Relative Cutter Power | ||||
|    * Normally, 'M3 O<power>' sets | ||||
|    * OCR power is relative to the range SPEED_POWER_MIN...SPEED_POWER_MAX. | ||||
|    * so input powers of 0...255 correspond to SPEED_POWER_MIN...SPEED_POWER_MAX | ||||
|    * instead of normal range (0 to SPEED_POWER_MAX). | ||||
|    * Best used with (e.g.) SuperPID router controller: 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 | ||||
|   //#define CUTTER_POWER_RELATIVE              // Set speed proportional to [SPEED_POWER_MIN...SPEED_POWER_MAX] | ||||
|  | ||||
|   #if ENABLED(SPINDLE_FEATURE) | ||||
|     //#define SPINDLE_CHANGE_DIR               // Enable if your spindle controller can change spindle direction | ||||
| @@ -2804,25 +2808,25 @@ | ||||
|     #define SPINDLE_LASER_POWERDOWN_DELAY 5000 // (ms) Delay to allow the spindle to stop | ||||
|  | ||||
|     /** | ||||
|      * 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 | ||||
|      * M3/M4 Power Equation | ||||
|      * | ||||
|      * Set these required parameters for your controller | ||||
|      * Each tool uses different value ranges for speed / power control. | ||||
|      * These parameters are used to convert between tool power units and PWM. | ||||
|      * | ||||
|      * Speed/Power = (PWMDC / 255 * 100 - SPEED_POWER_INTERCEPT) / SPEED_POWER_SLOPE | ||||
|      * PWMDC = (spdpwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) / SPEED_POWER_SLOPE | ||||
|      */ | ||||
|     #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 | ||||
|     #define SPEED_POWER_INTERCEPT         0    // (%) 0-100 i.e., Minimum power percentage | ||||
|     #define SPEED_POWER_MIN            5000    // (RPM) | ||||
|     #define SPEED_POWER_MAX           30000    // (RPM) SuperPID router controller 0 - 30,000 RPM | ||||
|     #define SPEED_POWER_STARTUP       25000    // (RPM) M3/M4 speed/power default (with no arguments) | ||||
|  | ||||
|   #else | ||||
|  | ||||
|     #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 | ||||
|     #define SPEED_POWER_INTERCEPT         0    // (%) 0-100 i.e., Minimum power percentage | ||||
|     #define SPEED_POWER_MIN               0    // (%) 0-100 | ||||
|     #define SPEED_POWER_MAX             100    // (%) 0-100 | ||||
|     #define SPEED_POWER_STARTUP          80    // (%) M3/M4 speed/power default (with no arguments) | ||||
|  | ||||
|     /** | ||||
|      * Enable inline laser power to be handled in the planner / stepper routines. | ||||
| @@ -2871,6 +2875,10 @@ | ||||
|         // 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 | ||||
|  | ||||
|         // Turn off the laser on G28 homing. | ||||
|         //#define LASER_MOVE_G28_OFF | ||||
|  | ||||
|       #endif | ||||
|  | ||||
|       /** | ||||
|   | ||||
| @@ -234,5 +234,55 @@ uint8_t extDigitalRead(const int8_t pin) { | ||||
|   } | ||||
| } | ||||
|  | ||||
| #if 0 | ||||
| /** | ||||
|  * Set Timer 5 PWM frequency in Hz, from 3.8Hz up to ~16MHz | ||||
|  * with a minimum resolution of 100 steps. | ||||
|  * | ||||
|  * DC values -1.0 to 1.0. Negative duty cycle inverts the pulse. | ||||
|  */ | ||||
| uint16_t set_pwm_frequency_hz(const float &hz, const float dca, const float dcb, const float dcc) { | ||||
|   float count = 0; | ||||
|   if (hz > 0 && (dca || dcb || dcc)) { | ||||
|     count = float(F_CPU) / hz;            // 1x prescaler, TOP for 16MHz base freq. | ||||
|     uint16_t prescaler;                   // Range of 30.5Hz (65535) 64.5KHz (>31) | ||||
|  | ||||
|          if (count >= 255. * 256.) { prescaler = 1024; SET_CS(5, PRESCALER_1024); } | ||||
|     else if (count >= 255. * 64.)  { prescaler = 256;  SET_CS(5,  PRESCALER_256); } | ||||
|     else if (count >= 255. * 8.)   { prescaler = 64;   SET_CS(5,   PRESCALER_64); } | ||||
|     else if (count >= 255.)        { prescaler = 8;    SET_CS(5,    PRESCALER_8); } | ||||
|     else                           { prescaler = 1;    SET_CS(5,    PRESCALER_1); } | ||||
|  | ||||
|     count /= float(prescaler); | ||||
|     const float pwm_top = round(count);   // Get the rounded count | ||||
|  | ||||
|     ICR5 = (uint16_t)pwm_top - 1;         // Subtract 1 for TOP | ||||
|     OCR5A = pwm_top * ABS(dca);          // Update and scale DCs | ||||
|     OCR5B = pwm_top * ABS(dcb); | ||||
|     OCR5C = pwm_top * ABS(dcc); | ||||
|     _SET_COM(5, A, dca ? (dca < 0 ? COM_SET_CLEAR : COM_CLEAR_SET) : COM_NORMAL); // Set compare modes | ||||
|     _SET_COM(5, B, dcb ? (dcb < 0 ? COM_SET_CLEAR : COM_CLEAR_SET) : COM_NORMAL); | ||||
|     _SET_COM(5, C, dcc ? (dcc < 0 ? COM_SET_CLEAR : COM_CLEAR_SET) : COM_NORMAL); | ||||
|  | ||||
|     SET_WGM(5, FAST_PWM_ICRn);            // Fast PWM with ICR5 as TOP | ||||
|  | ||||
|     //SERIAL_ECHOLNPGM("Timer 5 Settings:"); | ||||
|     //SERIAL_ECHOLNPAIR("  Prescaler=", prescaler); | ||||
|     //SERIAL_ECHOLNPAIR("        TOP=", ICR5); | ||||
|     //SERIAL_ECHOLNPAIR("      OCR5A=", OCR5A); | ||||
|     //SERIAL_ECHOLNPAIR("      OCR5B=", OCR5B); | ||||
|     //SERIAL_ECHOLNPAIR("      OCR5C=", OCR5C); | ||||
|   } | ||||
|   else { | ||||
|     // Restore the default for Timer 5 | ||||
|     SET_WGM(5, PWM_PC_8);                 // PWM 8-bit (Phase Correct) | ||||
|     SET_COMS(5, NORMAL, NORMAL, NORMAL);  // Do nothing | ||||
|     SET_CS(5, PRESCALER_64);              // 16MHz / 64 = 250KHz | ||||
|     OCR5A = OCR5B = OCR5C = 0; | ||||
|   } | ||||
|   return round(count); | ||||
| } | ||||
| #endif | ||||
|  | ||||
| #endif // FASTIO_EXT_START | ||||
| #endif // __AVR__ | ||||
|   | ||||
| @@ -31,12 +31,13 @@ | ||||
| #include "spindle_laser.h" | ||||
|  | ||||
| SpindleLaser cutter; | ||||
| uint8_t SpindleLaser::power; | ||||
| bool SpindleLaser::isReady;                                           // Ready to apply power setting from the UI to OCR | ||||
| cutter_power_t SpindleLaser::menuPower,                               // Power set via LCD menu in PWM, PERCENT, or RPM | ||||
|                SpindleLaser::unitPower;                               // LCD status power in PWM, PERCENT, or RPM | ||||
|  | ||||
| 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 | ||||
|   cutter_frequency_t SpindleLaser::frequency;                         // setting PWM frequency; range: 2K - 50K | ||||
| #endif | ||||
| #define SPINDLE_LASER_PWM_OFF ((SPINDLE_LASER_PWM_INVERT) ? 255 : 0) | ||||
|  | ||||
| @@ -44,13 +45,13 @@ cutter_setPower_t SpindleLaser::setPower = interpret_power(SPEED_POWER_MIN);   / | ||||
| // 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 | ||||
|   OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH);       // Init spindle to off | ||||
|   #if ENABLED(SPINDLE_CHANGE_DIR) | ||||
|     OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR ? 255 : 0);   // Init rotation to clockwise (M3) | ||||
|     OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR ? 255 : 0);         // Init rotation to clockwise (M3) | ||||
|   #endif | ||||
|   #if ENABLED(SPINDLE_LASER_PWM) | ||||
|     SET_PWM(SPINDLE_LASER_PWM_PIN); | ||||
|     analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF);  // set to lowest speed | ||||
|     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); | ||||
| @@ -59,38 +60,47 @@ void SpindleLaser::init() { | ||||
| } | ||||
|  | ||||
| #if ENABLED(SPINDLE_LASER_PWM) | ||||
|  | ||||
|   /** | ||||
|   * Set the cutter PWM directly to the given ocr value | ||||
|   **/ | ||||
|    * 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 | ||||
|     WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH);        // turn spindle on | ||||
|     analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF); | ||||
|   } | ||||
|  | ||||
|   void SpindleLaser::ocr_off() { | ||||
|     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 | ||||
|   } | ||||
| #endif | ||||
|  | ||||
| // | ||||
| // Set cutter ON state (and PWM) to the given cutter power value | ||||
| // Set cutter ON/OFF 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; | ||||
| void SpindleLaser::apply_power(const uint8_t opwr) { | ||||
|   static uint8_t last_power_applied = 0; | ||||
|   if (opwr == last_power_applied) return; | ||||
|   last_power_applied = opwr; | ||||
|   power = opwr; | ||||
|   #if ENABLED(SPINDLE_LASER_PWM) | ||||
|     if (enabled()) | ||||
|       set_ocr(translate_power(inpow)); | ||||
|     if (cutter.unitPower == 0 && CUTTER_UNIT_IS(RPM)) { | ||||
|       ocr_off(); | ||||
|       isReady = false; | ||||
|     } | ||||
|     else if (enabled() || ENABLED(CUTTER_POWER_RELATIVE)) { | ||||
|       set_ocr(power); | ||||
|       isReady = true; | ||||
|     } | ||||
|     else { | ||||
|       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 | ||||
|       ocr_off(); | ||||
|       isReady = false; | ||||
|     } | ||||
|   #else | ||||
|     WRITE(SPINDLE_LASER_ENA_PIN, (SPINDLE_LASER_ACTIVE_HIGH) ? enabled() : !enabled()); | ||||
|     WRITE(SPINDLE_LASER_ENA_PIN, enabled() == SPINDLE_LASER_ACTIVE_HIGH); | ||||
|     isReady = true; | ||||
|   #endif | ||||
| } | ||||
|  | ||||
| #if ENABLED(SPINDLE_CHANGE_DIR) | ||||
|  | ||||
|   // | ||||
|   // Set the spindle direction and apply immediately | ||||
|   // Stop on direction change if SPINDLE_STOP_ON_DIR_CHANGE is enabled | ||||
| @@ -100,7 +110,6 @@ void SpindleLaser::apply_power(const cutter_power_t inpow) { | ||||
|     if (TERN0(SPINDLE_STOP_ON_DIR_CHANGE, enabled()) && READ(SPINDLE_DIR_PIN) != dir_state) disable(); | ||||
|     WRITE(SPINDLE_DIR_PIN, dir_state); | ||||
|   } | ||||
|  | ||||
| #endif | ||||
|  | ||||
| #endif // HAS_CUTTER | ||||
|   | ||||
| @@ -34,87 +34,146 @@ | ||||
|   #include "../module/planner.h" | ||||
| #endif | ||||
|  | ||||
| #define PCT_TO_PWM(X) ((X) * 255 / 100) | ||||
|  | ||||
| #ifndef SPEED_POWER_INTERCEPT | ||||
|   #define SPEED_POWER_INTERCEPT 0 | ||||
| #endif | ||||
| #define SPEED_POWER_FLOOR TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0) | ||||
|  | ||||
| // #define _MAP(N,S1,S2,D1,D2) ((N)*_MAX((D2)-(D1),0)/_MAX((S2)-(S1),1)+(D1)) | ||||
|  | ||||
| class SpindleLaser { | ||||
| public: | ||||
|   static bool isOn;                             //  state to determine when to apply setPower to power | ||||
|   static cutter_power_t power; | ||||
|   static cutter_setPower_t setPower;            //  spindle/laser menu set power; in PWM, Percentage or RPM | ||||
|   static constexpr float | ||||
|     min_pct = round(TERN(CUTTER_POWER_RELATIVE, 0, (100 * float(SPEED_POWER_MIN) / TERN(SPINDLE_FEATURE, float(SPEED_POWER_MAX), 100)))), | ||||
|     max_pct = round(TERN(SPINDLE_FEATURE, 100, float(SPEED_POWER_MAX))); | ||||
|  | ||||
|   static const inline uint8_t pct_to_ocr(const float pct) { return uint8_t(PCT_TO_PWM(pct)); } | ||||
|  | ||||
|   // cpower = configured values (ie SPEED_POWER_MAX) | ||||
|   static const inline uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) { // configured value to pct | ||||
|     return unitPower ? round(100 * (cpwr - SPEED_POWER_FLOOR) / (SPEED_POWER_MAX - SPEED_POWER_FLOOR)) : 0; | ||||
|   } | ||||
|  | ||||
|   // Convert a configured value (cpower)(ie SPEED_POWER_STARTUP) to unit power (upwr, upower), | ||||
|   // which can be PWM, Percent, or RPM (rel/abs). | ||||
|   static const inline cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power | ||||
|     const cutter_power_t upwr = ( | ||||
|       #if ENABLED(SPINDLE_FEATURE) | ||||
|         // Spindle configured values are in RPM | ||||
|         #if CUTTER_UNIT_IS(RPM) | ||||
|           cpwr                            // to RPM | ||||
|         #elif CUTTER_UNIT_IS(PERCENT)     // to PCT | ||||
|           cpwr_to_pct(cpwr) | ||||
|         #else                             // to PWM | ||||
|           PCT_TO_PWM(cpwr_to_pct(cpwr)) | ||||
|         #endif | ||||
|       #else | ||||
|         // Laser configured values are in PCT | ||||
|         #if CUTTER_UNIT_IS(PWM255) | ||||
|           PCT_TO_PWM(cpwr) | ||||
|         #else | ||||
|           cpwr                            // to RPM/PCT | ||||
|         #endif | ||||
|       #endif | ||||
|     ); | ||||
|     return upwr; | ||||
|   } | ||||
|  | ||||
|   static const cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); } | ||||
|   static const cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); } | ||||
|  | ||||
|   static bool isReady;                    // Ready to apply power setting from the UI to OCR | ||||
|   static uint8_t power; | ||||
|  | ||||
|   #if ENABLED(MARLIN_DEV_MODE) | ||||
|     static cutter_frequency_t frequency;        //  set PWM frequency; range: 2K-50K | ||||
|     static cutter_frequency_t frequency;  // Set PWM frequency; range: 2K-50K | ||||
|   #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 cutter_power_t menuPower;        // Power as set via LCD menu in PWM, Percentage or RPM | ||||
|   static cutter_power_t unitPower;        // Power as displayed status in PWM, Percentage or RPM | ||||
|  | ||||
|   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); | ||||
|  | ||||
|   // Modifying this function should update everywhere | ||||
|   static inline bool enabled(const cutter_power_t opwr) { return opwr > 0; } | ||||
|   static inline bool enabled() { return enabled(power); } | ||||
|  | ||||
|   static void apply_power(const uint8_t inpow); | ||||
|  | ||||
|   FORCE_INLINE static void refresh() { apply_power(power); } | ||||
|   FORCE_INLINE static void set_power(const cutter_power_t pwr) { power = pwr; refresh(); } | ||||
|   FORCE_INLINE static void set_power(const uint8_t upwr) { power = upwr; refresh(); } | ||||
|  | ||||
|   static inline void set_enabled(const bool enable) { set_power(enable ? (power ?: interpret_power(SPEED_POWER_STARTUP)) : 0); } | ||||
|   static inline void set_enabled(const bool enable) { set_power(enable ? (power ?: (unitPower ? upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP)) : 0)) : 0); } | ||||
|  | ||||
|   #if ENABLED(SPINDLE_LASER_PWM) | ||||
|  | ||||
|     static void set_ocr(const uint8_t ocr); | ||||
|     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 | ||||
|     static inline void set_ocr_power(const uint8_t ocr) { power = ocr; set_ocr(ocr); } | ||||
|     static void ocr_off(); | ||||
|     // Used to update output for power->OCR translation | ||||
|     static inline uint8_t upower_to_ocr(const cutter_power_t upwr) { | ||||
|       return ( | ||||
|         #if CUTTER_UNIT_IS(PWM255) | ||||
|           uint8_t(upwr) | ||||
|         #elif CUTTER_UNIT_IS(PERCENT) | ||||
|           pct_to_ocr(upwr) | ||||
|         #else | ||||
|           uint8_t(pct_to_ocr(cpwr_to_pct(upwr))) | ||||
|         #endif | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     // Correct power to configured range | ||||
|     static inline cutter_power_t power_to_range(const cutter_power_t pwr) { | ||||
|       return power_to_range(pwr, ( | ||||
|         #if CUTTER_UNIT_IS(PWM255) | ||||
|           0 | ||||
|         #elif CUTTER_UNIT_IS(PERCENT) | ||||
|           1 | ||||
|         #elif CUTTER_UNIT_IS(RPM) | ||||
|           2 | ||||
|         #else | ||||
|           #error "???" | ||||
|         #endif | ||||
|       )); | ||||
|     } | ||||
|     static inline cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit) { | ||||
|       if (pwr <= 0) return 0; | ||||
|       cutter_power_t upwr; | ||||
|       switch (pwrUnit) { | ||||
|         case 0:                                                 // PWM | ||||
|           upwr = ( | ||||
|               (pwr < pct_to_ocr(min_pct)) ? pct_to_ocr(min_pct) // Use minimum if set below | ||||
|             : (pwr > pct_to_ocr(max_pct)) ? pct_to_ocr(max_pct) // Use maximum if set above | ||||
|             :  pwr | ||||
|           ); | ||||
|           break; | ||||
|         case 1:                                                 // PERCENT | ||||
|           upwr = ( | ||||
|               (pwr < min_pct) ? min_pct                         // Use minimum if set below | ||||
|             : (pwr > max_pct) ? max_pct                         // Use maximum if set above | ||||
|             :  pwr                                              // PCT | ||||
|           ); | ||||
|           break; | ||||
|         case 2:                                                 // RPM | ||||
|           upwr = ( | ||||
|               (pwr < SPEED_POWER_MIN) ? SPEED_POWER_MIN         // Use minimum if set below | ||||
|             : (pwr > SPEED_POWER_MAX) ? SPEED_POWER_MAX         // Use maximum if set above | ||||
|             : pwr                                               // Calculate OCR value | ||||
|           ); | ||||
|           break; | ||||
|         default: break; | ||||
|       } | ||||
|       return upwr; | ||||
|     } | ||||
|  | ||||
|   #endif // SPINDLE_LASER_PWM | ||||
|  | ||||
|   // Wait for spindle to spin up or spin down | ||||
|   static inline void power_delay(const bool on) { | ||||
| @@ -129,37 +188,82 @@ public: | ||||
|     static inline void set_direction(const bool) {} | ||||
|   #endif | ||||
|  | ||||
|   static inline void disable() { isOn = false; set_enabled(false); } | ||||
|   static inline void disable() { isReady = 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); } | ||||
|  | ||||
|     static inline void enable_with_dir(const bool reverse) { | ||||
|       isReady = true; | ||||
|       const uint8_t ocr = upower_to_ocr(menuPower); | ||||
|       if (menuPower) | ||||
|         power = ocr; | ||||
|       else | ||||
|         menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP); | ||||
|       unitPower = menuPower; | ||||
|       set_direction(reverse); | ||||
|       set_enabled(true); | ||||
|     } | ||||
|     FORCE_INLINE static void enable_forward() { enable_with_dir(false); } | ||||
|     FORCE_INLINE static void enable_reverse() { enable_with_dir(true); } | ||||
|  | ||||
|     #if ENABLED(SPINDLE_LASER_PWM) | ||||
|       static inline void update_from_mpower() { | ||||
|         if (isReady) power = upower_to_ocr(menuPower); | ||||
|         unitPower = menuPower; | ||||
|       } | ||||
|     #endif | ||||
|  | ||||
|   #endif | ||||
|  | ||||
|   #if ENABLED(LASER_POWER_INLINE) | ||||
|     /** | ||||
|      * Inline power adds extra fields to the planner block | ||||
|      * to handle laser power and scale to movement speed. | ||||
|      */ | ||||
|  | ||||
|     // Force disengage planner power control | ||||
|     static inline void inline_disable() { planner.laser.status = 0; planner.laser.power = 0; isOn = false;} | ||||
|     static inline void inline_disable()	{ | ||||
|       isReady = false; | ||||
|       unitPower = 0; | ||||
|       planner.laser_inline.status = 0; | ||||
|       planner.laser_inline.power = 0; | ||||
|     } | ||||
|  | ||||
|     // 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 inline void set_inline_enabled(const bool enable) { | ||||
|       if (enable) { inline_power(cpwr_to_upwr(SPEED_POWER_STARTUP)); } | ||||
|       else { unitPower = 0; isReady = false; menuPower = 0; TERN(SPINDLE_LASER_PWM, inline_ocr_power, inline_power)(0);} | ||||
|     } | ||||
|  | ||||
|     static void inline_power(const cutter_power_t pwr) { | ||||
|     // Set the power for subsequent movement blocks | ||||
|     static void inline_power(const cutter_power_t upwr) { | ||||
|       unitPower = upwr; | ||||
|       menuPower = unitPower; | ||||
|       #if ENABLED(SPINDLE_LASER_PWM) | ||||
|         inline_ocr_power(translate_power(pwr)); | ||||
|           isReady = true; | ||||
|         #if ENABLED(SPEED_POWER_RELATIVE) && !CUTTER_UNIT_IS(RPM) // relative mode does not turn laser off at 0, except for RPM | ||||
|           planner.laser_inline.status = 0x03; | ||||
|           planner.laser_inline.power = upower_to_ocr(upwr); | ||||
|         #else | ||||
|           if (upwr > 0) | ||||
|             inline_ocr_power(upower_to_ocr(upwr)); | ||||
|         #endif | ||||
|       #else | ||||
|         planner.laser.status = enabled(pwr) ? 0x03 : 0x01; | ||||
|         planner.laser.power = pwr; | ||||
|         planner.laser_inline.status = enabled(pwr) ? 0x03 : 0x01; | ||||
|         planner.laser_inline.power = pwr; | ||||
|         isReady = enabled(upwr); | ||||
|       #endif | ||||
|     } | ||||
|  | ||||
|     static inline void inline_direction(const bool reverse) { UNUSED(reverse); } // TODO is this ever going to be needed | ||||
|     static inline void inline_direction(const bool) { /* never */ } | ||||
|  | ||||
|     #if ENABLED(SPINDLE_LASER_PWM) | ||||
|       static inline void inline_ocr_power(const uint8_t pwr) { | ||||
|         planner.laser.status = pwr ? 0x03 : 0x01; | ||||
|         planner.laser.power = pwr; | ||||
|       static inline void inline_ocr_power(const uint8_t ocrpwr) { | ||||
|         planner.laser_inline.status = ocrpwr ? 0x03 : 0x01; | ||||
|         planner.laser_inline.power = ocrpwr; | ||||
|       } | ||||
|     #endif | ||||
|   #endif | ||||
|   #endif  // LASER_POWER_INLINE | ||||
|  | ||||
|   static inline void kill() { | ||||
|     TERN_(LASER_POWER_INLINE, inline_disable()); | ||||
|   | ||||
| @@ -34,19 +34,20 @@ | ||||
|   #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 | ||||
|   #define cutter_power2str            ui16tostr5rj | ||||
|  | ||||
| typedef IF<(SPEED_POWER_MAX > 255), uint16_t, uint8_t>::type cutter_cpower_t; | ||||
|  | ||||
| #if CUTTER_UNIT_IS(RPM) && SPEED_POWER_MAX > 255 | ||||
|   typedef uint16_t cutter_power_t; | ||||
|   #define CUTTER_MENU_POWER_TYPE uint16_5 | ||||
|   #define cutter_power2str       ui16tostr5rj | ||||
| #else | ||||
|   #define cutter_power_t              uint8_t | ||||
|   #define cutter_setPower_t           uint8_t | ||||
|   #define CUTTER_MENU_POWER_TYPE      uint8 | ||||
|   #define cutter_power2str            ui8tostr3rj | ||||
|   typedef uint8_t cutter_power_t; | ||||
|   #define CUTTER_MENU_POWER_TYPE uint8 | ||||
|   #define cutter_power2str       ui8tostr3rj | ||||
| #endif | ||||
|  | ||||
| #if ENABLED(MARLIN_DEV_MODE) | ||||
|   #define cutter_frequency_t          uint16_t | ||||
|   #define CUTTER_MENU_FREQUENCY_TYPE  uint16_5 | ||||
|   typedef uint16_t cutter_frequency_t; | ||||
|   #define CUTTER_MENU_FREQUENCY_TYPE uint16_5 | ||||
| #endif | ||||
|   | ||||
| @@ -51,6 +51,10 @@ | ||||
|   #include "../../libs/L64XX/L64XX_Marlin.h" | ||||
| #endif | ||||
|  | ||||
| #if ENABLED(LASER_MOVE_G28_OFF) | ||||
|   #include "../../feature/spindle_laser.h" | ||||
| #endif | ||||
|  | ||||
| #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) | ||||
| #include "../../core/debug_out.h" | ||||
|  | ||||
| @@ -195,6 +199,11 @@ | ||||
|  * | ||||
|  */ | ||||
| void GcodeSuite::G28() { | ||||
|  | ||||
| #if ENABLED(LASER_MOVE_G28_OFF) | ||||
|   cutter.set_inline_enabled(false);       // turn off laser | ||||
| #endif | ||||
|  | ||||
|   if (DEBUGGING(LEVELING)) { | ||||
|     DEBUG_ECHOLNPGM(">>> G28"); | ||||
|     log_machine_info(); | ||||
|   | ||||
| @@ -28,28 +28,18 @@ | ||||
| #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: | ||||
|  * | ||||
|  *  M3 - Laser ON/Power (Ramped power) | ||||
|  *  M4 - Laser ON/Power (Continuous power) | ||||
|  * | ||||
|  *    S<power> - Set power. S0 will turn the laser off. | ||||
|  *    O<ocr>   - Set power and OCR | ||||
|  * | ||||
|  * Spindle: | ||||
|  * | ||||
|  *  M3 - Spindle ON (Clockwise) | ||||
|  *  M4 - Spindle ON (Counter-clockwise) | ||||
|  * | ||||
|  *    S<power> - Set power. S0 will turn the spindle off. | ||||
|  *    O<ocr>   - Set power and OCR | ||||
|  * Parameters: | ||||
|  *  S<power> - Set power. S0 will turn the spindle/laser off, except in relative mode. | ||||
|  *  O<ocr>   - Set power and OCR (oscillator count register) | ||||
|  * | ||||
|  *  If no PWM pin is defined then M3/M4 just turns it on. | ||||
|  * | ||||
| @@ -76,17 +66,25 @@ inline cutter_power_t get_s_power() { | ||||
|  *  PWM duty cycle goes from 0 (off) to 255 (always on). | ||||
|  */ | ||||
| void GcodeSuite::M3_M4(const bool is_M4) { | ||||
|   auto get_s_power = [] { | ||||
|     if (parser.seen('S')) | ||||
|       cutter.unitPower = cutter.power_to_range(cutter_power_t(round(parser.value_float()))); | ||||
|     else | ||||
|       cutter.unitPower = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP); | ||||
|     return cutter.unitPower; | ||||
|   }; | ||||
|  | ||||
|   #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) | ||||
|         if (parser.seen('O')) { | ||||
|           cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0); | ||||
|           cutter.inline_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t) | ||||
|         } | ||||
|         else | ||||
|           cutter.inline_power(get_s_power()); | ||||
|           cutter.inline_power(cutter.upower_to_ocr(get_s_power())); | ||||
|       #else | ||||
|         cutter.inline_enabled(true); | ||||
|       #endif | ||||
| @@ -97,17 +95,19 @@ void GcodeSuite::M3_M4(const bool is_M4) { | ||||
|   #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) | ||||
|     if (parser.seenval('O')) { | ||||
|       cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0); | ||||
|       cutter.set_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t) | ||||
|     } | ||||
|     else | ||||
|       cutter.set_power(get_s_power()); | ||||
|       cutter.set_power(cutter.upower_to_ocr(get_s_power())); | ||||
|   #else | ||||
|     cutter.set_enabled(true); | ||||
|   #endif | ||||
|   cutter.menuPower = cutter.unitPower; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -116,7 +116,7 @@ void GcodeSuite::M3_M4(const bool is_M4) { | ||||
| void GcodeSuite::M5() { | ||||
|   #if ENABLED(LASER_POWER_INLINE) | ||||
|     if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) { | ||||
|       cutter.inline_enabled(false); // Laser power in inline mode | ||||
|       cutter.set_inline_enabled(false); // Laser power in inline mode | ||||
|       return; | ||||
|     } | ||||
|     // Non-inline, standard case | ||||
| @@ -124,6 +124,7 @@ void GcodeSuite::M5() { | ||||
|   #endif | ||||
|   planner.synchronize(); | ||||
|   cutter.set_enabled(false); | ||||
|   cutter.menuPower = cutter.unitPower; | ||||
| } | ||||
|  | ||||
| #endif // HAS_CUTTER | ||||
|   | ||||
| @@ -180,13 +180,9 @@ void GcodeSuite::get_destination_from_command() { | ||||
|   #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 | ||||
|     } | ||||
|       cutter.inline_power(cutter.power_to_range(cutter_power_t(round(parser.value_float())))); | ||||
|     else if (ENABLED(LASER_MOVE_G0_OFF) && parser.codenum == 0) // G0 | ||||
|       cutter.set_inline_enabled(false); | ||||
|   #endif | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -142,11 +142,11 @@ | ||||
| // | ||||
| #if EITHER(SPINDLE_FEATURE, LASER_FEATURE) | ||||
|   #define HAS_CUTTER 1 | ||||
|   #define _CUTTER_DISP_PWM255  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)) | ||||
|   #define _CUTTER_POWER_PWM255  1 | ||||
|   #define _CUTTER_POWER_PERCENT 2 | ||||
|   #define _CUTTER_POWER_RPM     3 | ||||
|   #define _CUTTER_POWER(V)      _CAT(_CUTTER_POWER_, V) | ||||
|   #define CUTTER_UNIT_IS(V)    (_CUTTER_POWER(CUTTER_POWER_UNIT)    == _CUTTER_POWER(V)) | ||||
| #endif | ||||
|  | ||||
| // Add features that need hardware PWM here | ||||
|   | ||||
| @@ -421,6 +421,8 @@ | ||||
|   #error "SPINDLE_STOP_ON_DIR_CHANGE is now SPINDLE_CHANGE_DIR_STOP. Please update your Configuration_adv.h." | ||||
| #elif defined(SPINDLE_LASER_ENABLE_INVERT) | ||||
|   #error "SPINDLE_LASER_ENABLE_INVERT is now SPINDLE_LASER_ACTIVE_HIGH. Please update your Configuration_adv.h." | ||||
| #elif defined(CUTTER_POWER_DISPLAY) | ||||
|   #error "CUTTER_POWER_DISPLAY is now CUTTER_POWER_UNIT. Please update your Configuration_adv.h." | ||||
| #elif defined(CHAMBER_HEATER_PIN) | ||||
|   #error "CHAMBER_HEATER_PIN is now HEATER_CHAMBER_PIN. Please update your configuration and/or pins." | ||||
| #elif defined(TMC_Z_CALIBRATION) | ||||
| @@ -2825,10 +2827,10 @@ 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(PWM255) && !CUTTER_DISPLAY_IS(PERCENT) && !CUTTER_DISPLAY_IS(RPM) | ||||
|     #error "CUTTER_POWER_DISPLAY must be PWM255, PERCENT, or RPM. Please update your Configuration_adv.h." | ||||
|   #ifndef CUTTER_POWER_UNIT | ||||
|     #error "CUTTER_POWER_UNIT is required with a spindle or laser. Please update your Configuration_adv.h." | ||||
|   #elif !CUTTER_UNIT_IS(PWM255) && !CUTTER_UNIT_IS(PERCENT) && !CUTTER_UNIT_IS(RPM) | ||||
|     #error "CUTTER_POWER_UNIT must be PWM255, PERCENT, or RPM. Please update your Configuration_adv.h." | ||||
|   #endif | ||||
|  | ||||
|   #if ENABLED(LASER_POWER_INLINE) | ||||
| @@ -2878,7 +2880,7 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2) | ||||
|       #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin." | ||||
|     #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) || !defined(SPEED_POWER_STARTUP) | ||||
|     #elif !(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." | ||||
|   | ||||
| @@ -541,12 +541,15 @@ 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, cutter_power2str(cutter.power)); | ||||
|         #if CUTTER_DISPLAY_IS(PERCENT) | ||||
|       if (cutter.isReady && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) { | ||||
|         #if CUTTER_UNIT_IS(PERCENT) | ||||
|           lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, cutter_power2str(cutter.unitPower)); | ||||
|           lcd_put_wchar('%'); | ||||
|         #elif CUTTER_DISPLAY_IS(RPM) | ||||
|         #elif CUTTER_UNIT_IS(RPM) | ||||
|           lcd_put_u8str(STATUS_CUTTER_TEXT_X - 2, STATUS_CUTTER_TEXT_Y, ftostr51rj(float(cutter.unitPower) / 1000)); | ||||
|           lcd_put_wchar('K'); | ||||
|         #else | ||||
|           lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, cutter_power2str(cutter.unitPower)); | ||||
|         #endif | ||||
|       } | ||||
|     #endif | ||||
|   | ||||
| @@ -73,7 +73,6 @@ void menu_configuration(); | ||||
| #endif | ||||
|  | ||||
| #if HAS_CUTTER | ||||
|   #include "../../feature/spindle_laser.h" | ||||
|   void menu_spindle_laser(); | ||||
| #endif | ||||
|  | ||||
|   | ||||
| @@ -34,19 +34,19 @@ | ||||
|  | ||||
|   void menu_spindle_laser() { | ||||
|  | ||||
|     const bool can_disable = cutter.enabled() && cutter.isOn; | ||||
|     const bool is_enabled = cutter.enabled() && cutter.isReady; | ||||
|  | ||||
|     START_MENU(); | ||||
|     BACK_ITEM(MSG_MAIN); | ||||
|  | ||||
|     #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; } | ||||
|       ); | ||||
|       // Change the cutter's "current power" value without turning the cutter on or off | ||||
|       // Power is displayed and set in units and range according to CUTTER_POWER_UNIT | ||||
|       EDIT_ITEM_FAST(CUTTER_MENU_POWER_TYPE, MSG_CUTTER(POWER), &cutter.menuPower, | ||||
|         cutter.mpower_min(), cutter.mpower_max(), cutter.update_from_mpower); | ||||
|     #endif | ||||
|  | ||||
|     if (can_disable) | ||||
|     if (is_enabled) | ||||
|       ACTION_ITEM(MSG_CUTTER(OFF), cutter.disable); | ||||
|     else { | ||||
|       ACTION_ITEM(MSG_CUTTER(ON), cutter.enable_forward); | ||||
| @@ -57,7 +57,7 @@ | ||||
|  | ||||
|     #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();}); | ||||
|         EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 50000, cutter.refresh_frequency); | ||||
|       #endif | ||||
|     #endif | ||||
|     END_MENU(); | ||||
|   | ||||
| @@ -129,7 +129,7 @@ uint8_t Planner::delay_before_delivering;       // This counter delays delivery | ||||
| planner_settings_t Planner::settings;           // Initialized by settings.load() | ||||
|  | ||||
| #if ENABLED(LASER_POWER_INLINE) | ||||
|   laser_state_t Planner::laser;              // Current state for blocks | ||||
|   laser_state_t Planner::laser_inline;          // Current state for blocks | ||||
| #endif | ||||
|  | ||||
| uint32_t Planner::max_acceleration_steps_per_s2[XYZE_N]; // (steps/s^2) Derived from mm_per_s2 | ||||
| @@ -1693,7 +1693,7 @@ bool Planner::_buffer_steps(const xyze_long_t &target | ||||
|  *  fr_mm_s     - (target) speed of the move | ||||
|  *  extruder    - target extruder | ||||
|  * | ||||
|  * Returns true is movement is acceptable, false otherwise | ||||
|  * Returns true if movement is acceptable, false otherwise | ||||
|  */ | ||||
| bool Planner::_populate_block(block_t * const block, bool split_move, | ||||
|   const abce_long_t &target | ||||
| @@ -1803,8 +1803,8 @@ bool Planner::_populate_block(block_t * const block, bool split_move, | ||||
|  | ||||
|   // Update block laser power | ||||
|   #if ENABLED(LASER_POWER_INLINE) | ||||
|     block->laser.status = laser.status; | ||||
|     block->laser.power = laser.power; | ||||
|     block->laser.status = laser_inline.status; | ||||
|     block->laser.power = laser_inline.power; | ||||
|   #endif | ||||
|  | ||||
|   // Number of steps for each axis | ||||
|   | ||||
| @@ -117,8 +117,15 @@ enum BlockFlag : char { | ||||
| #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 | ||||
|     bool isPlanned:1; | ||||
|     bool isEnabled:1; | ||||
|     bool dir:1; | ||||
|     bool Reserved:6; | ||||
|   } power_status_t; | ||||
|  | ||||
|   typedef struct { | ||||
|     power_status_t status;    // See planner settings for meaning | ||||
|     uint8_t 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) | ||||
| @@ -234,18 +241,15 @@ typedef struct block_t { | ||||
| #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 | ||||
|      * Laser status flags | ||||
|      */ | ||||
|     uint8_t status; | ||||
|     power_status_t status; | ||||
|     /** | ||||
|      * Laser power: 0 or 255 in case of PWM-less laser, | ||||
|      * or the OCR value; | ||||
|      * or the OCR (oscillator count register) value; | ||||
|      * | ||||
|      * Using OCR instead of raw power, | ||||
|      * as it avoids floating points during move loop | ||||
|      * Using OCR instead of raw power, because it avoids | ||||
|      * floating point operations during the move loop. | ||||
|      */ | ||||
|     uint8_t power; | ||||
|   } laser_state_t; | ||||
| @@ -332,7 +336,7 @@ class Planner { | ||||
|     static planner_settings_t settings; | ||||
|  | ||||
|     #if ENABLED(LASER_POWER_INLINE) | ||||
|       static laser_state_t laser; | ||||
|       static laser_state_t laser_inline; | ||||
|     #endif | ||||
|  | ||||
|     static uint32_t max_acceleration_steps_per_s2[XYZE_N]; // (steps/s^2) Derived from mm_per_s2 | ||||
|   | ||||
| @@ -244,8 +244,8 @@ 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, | ||||
|   Stepper::stepper_laser_t Stepper::laser_trap = { | ||||
|     .enabled = false, | ||||
|     .cur_power = 0, | ||||
|     .cruise_set = false, | ||||
|     #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) | ||||
| @@ -1843,28 +1843,28 @@ uint32_t Stepper::block_phase_isr() { | ||||
|  | ||||
|         // Update laser - Accelerating | ||||
|         #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) | ||||
|           if (laser.trap_en) { | ||||
|           if (laser_trap.enabled) { | ||||
|             #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; | ||||
|                 laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count; | ||||
|                 laser_trap.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++; | ||||
|                 if (laser_trap.acc_step_count < 0) { | ||||
|                   while (laser_trap.acc_step_count < 0) { | ||||
|                     laser_trap.acc_step_count += current_block->laser.entry_per; | ||||
|                     if (laser_trap.cur_power < current_block->laser.power) laser_trap.cur_power++; | ||||
|                   } | ||||
|                   cutter.set_ocr_power(laser.cur_power); | ||||
|                   cutter.set_ocr_power(laser_trap.cur_power); | ||||
|                 } | ||||
|               } | ||||
|             #else | ||||
|               if (laser.till_update) | ||||
|                 laser.till_update--; | ||||
|               if (laser_trap.till_update) | ||||
|                 laser_trap.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 | ||||
|                 laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER; | ||||
|                 laser_trap.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate; | ||||
|                 cutter.set_ocr_power(laser_trap.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles | ||||
|               } | ||||
|             #endif | ||||
|           } | ||||
| @@ -1920,28 +1920,28 @@ uint32_t Stepper::block_phase_isr() { | ||||
|  | ||||
|         // Update laser - Decelerating | ||||
|         #if ENABLED(LASER_POWER_INLINE_TRAPEZOID) | ||||
|           if (laser.trap_en) { | ||||
|           if (laser_trap.enabled) { | ||||
|             #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; | ||||
|                 laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count; | ||||
|                 laser_trap.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--; | ||||
|                 if (laser_trap.acc_step_count < 0) { | ||||
|                   while (laser_trap.acc_step_count < 0) { | ||||
|                     laser_trap.acc_step_count += current_block->laser.exit_per; | ||||
|                     if (laser_trap.cur_power > current_block->laser.power_exit) laser_trap.cur_power--; | ||||
|                   } | ||||
|                   cutter.set_ocr_power(laser.cur_power); | ||||
|                   cutter.set_ocr_power(laser_trap.cur_power); | ||||
|                 } | ||||
|               } | ||||
|             #else | ||||
|               if (laser.till_update) | ||||
|                 laser.till_update--; | ||||
|               if (laser_trap.till_update) | ||||
|                 laser_trap.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 | ||||
|                 laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER; | ||||
|                 laser_trap.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate; | ||||
|                 cutter.set_ocr_power(laser_trap.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles | ||||
|               } | ||||
|             #endif | ||||
|           } | ||||
| @@ -1966,16 +1966,16 @@ uint32_t Stepper::block_phase_isr() { | ||||
|  | ||||
|         // 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 (laser_trap.enabled) { | ||||
|             if (!laser_trap.cruise_set) { | ||||
|               laser_trap.cur_power = current_block->laser.power; | ||||
|               cutter.set_ocr_power(laser_trap.cur_power); | ||||
|               laser_trap.cruise_set = true; | ||||
|             } | ||||
|             #if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT) | ||||
|               laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER; | ||||
|               laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER; | ||||
|             #else | ||||
|               laser.last_step_count = step_events_completed; | ||||
|               laser_trap.last_step_count = step_events_completed; | ||||
|             #endif | ||||
|           } | ||||
|         #endif | ||||
| @@ -2000,7 +2000,10 @@ uint32_t Stepper::block_phase_isr() { | ||||
|           return interval; // No more queued movements! | ||||
|       } | ||||
|  | ||||
|       TERN_(HAS_CUTTER, cutter.apply_power(current_block->cutter_power)); | ||||
|       // For non-inline cutter, grossly apply power | ||||
|       #if ENABLED(LASER_FEATURE) && DISABLED(LASER_POWER_INLINE) | ||||
|         cutter.apply_power(current_block->cutter_power); | ||||
|       #endif | ||||
|  | ||||
|       TERN_(POWER_LOSS_RECOVERY, recovery.info.sdpos = current_block->sdpos); | ||||
|  | ||||
| @@ -2150,15 +2153,9 @@ uint32_t Stepper::block_phase_isr() { | ||||
|         else LA_isr_rate = LA_ADV_NEVER; | ||||
|       #endif | ||||
|  | ||||
|       if ( | ||||
|         #if HAS_L64XX | ||||
|           true  // Always set direction for L64xx (This also enables the chips) | ||||
|         #else | ||||
|           current_block->direction_bits != last_direction_bits | ||||
|           #if DISABLED(MIXING_EXTRUDER) | ||||
|             || stepper_extruder != last_moved_extruder | ||||
|           #endif | ||||
|         #endif | ||||
|       if ( ENABLED(HAS_L64XX)  // Always set direction for L64xx (Also enables the chips) | ||||
|         || current_block->direction_bits != last_direction_bits | ||||
|         || TERN(MIXING_EXTRUDER, false, stepper_extruder != last_moved_extruder) | ||||
|       ) { | ||||
|         last_direction_bits = current_block->direction_bits; | ||||
|         #if EXTRUDERS > 1 | ||||
| @@ -2170,33 +2167,31 @@ uint32_t Stepper::block_phase_isr() { | ||||
|       } | ||||
|  | ||||
|       #if ENABLED(LASER_POWER_INLINE) | ||||
|         const uint8_t stat = current_block->laser.status; | ||||
|         const power_status_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; | ||||
|           laser_trap.enabled = stat.isPlanned && stat.isEnabled; | ||||
|           laser_trap.cur_power = current_block->laser.power_entry; // RESET STATE | ||||
|           laser_trap.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; | ||||
|             laser_trap.last_step_count = 0; | ||||
|             laser_trap.acc_step_count = current_block->laser.entry_per / 2; | ||||
|           #else | ||||
|             laser.till_update = 0; | ||||
|             laser_trap.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); | ||||
|           if (stat.isPlanned) {                        // Planner controls the laser | ||||
|             cutter.set_ocr_power( | ||||
|               stat.isEnabled ? laser_trap.cur_power : 0 // ON with power or OFF | ||||
|             ); | ||||
|           } | ||||
|         #else | ||||
|           if (TEST(stat, 0)) {                        // Planner controls the laser | ||||
|           if (stat.isPlanned) {                        // 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); | ||||
|               cutter.set_ocr_power( | ||||
|                 stat.isEnabled ? current_block->laser.power : 0 // ON with power or OFF | ||||
|               ); | ||||
|             #else | ||||
|               cutter.set_enabled(TEST(stat, 1)); | ||||
|               cutter.set_enabled(stat.isEnabled); | ||||
|             #endif | ||||
|           } | ||||
|         #endif | ||||
| @@ -2237,15 +2232,14 @@ uint32_t Stepper::block_phase_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.laser.status; | ||||
|         if (TEST(stat, 0)) {             // Planner controls the laser | ||||
|         const power_status_t stat = planner.laser_inline.status; | ||||
|         if (stat.isPlanned) {             // Planner controls the laser | ||||
|           #if ENABLED(SPINDLE_LASER_PWM) | ||||
|             if (TEST(stat, 1))           // Laser is on | ||||
|               cutter.set_ocr_power(planner.laser.power); | ||||
|             else | ||||
|               cutter.set_power(0); | ||||
|             cutter.set_ocr_power( | ||||
|               stat.isEnabled ? planner.laser_inline.power : 0 // ON with power or OFF | ||||
|             ); | ||||
|           #else | ||||
|             cutter.set_enabled(TEST(stat, 1)); | ||||
|             cutter.set_enabled(stat.isEnabled); | ||||
|           #endif | ||||
|         } | ||||
|       } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user