Browse Source

Various Laser / Spindle improvements (#15335)

Ben 4 years ago
parent
commit
df8b7dfc40
No account linked to committer's email address

+ 107
- 15
Marlin/Configuration_adv.h View File

@@ -2662,31 +2662,123 @@
2662 2662
   #define SPINDLE_LASER_ACTIVE_HIGH     false  // Set to "true" if the on/off function is active HIGH
2663 2663
   #define SPINDLE_LASER_PWM             true   // Set to "true" if your controller supports setting the speed/power
2664 2664
   #define SPINDLE_LASER_PWM_INVERT      true   // Set to "true" if the speed/power goes up when you want it to go slower
2665
-  #define SPINDLE_LASER_POWERUP_DELAY   5000   // (ms) Delay to allow the spindle/laser to come up to speed/power
2666
-  #define SPINDLE_LASER_POWERDOWN_DELAY 5000   // (ms) Delay to allow the spindle to stop
2665
+
2666
+  #define SPINDLE_LASER_FREQUENCY       2500   // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC)
2667
+
2668
+  /**
2669
+   * Speed / Power can be set ('M3 S') and displayed in terms of:
2670
+   *  - PWM     (S0 - S255)
2671
+   *  - PERCENT (S0 - S100)
2672
+   *  - RPM     (S0 - S50000)  Best for use with a spindle
2673
+   */
2674
+  #define CUTTER_POWER_DISPLAY PWM
2675
+
2676
+  /**
2677
+   * Relative mode uses relative range (SPEED_POWER_MIN to SPEED_POWER_MAX) instead of normal range (0 to SPEED_POWER_MAX)
2678
+   * Best use with SuperPID router controller where for example S0 = 5,000 RPM and S255 = 30,000 RPM
2679
+   */
2680
+  //#define CUTTER_POWER_RELATIVE              // Set speed proportional to [SPEED_POWER_MIN...SPEED_POWER_MAX] instead of directly
2667 2681
 
2668 2682
   #if ENABLED(SPINDLE_FEATURE)
2669 2683
     //#define SPINDLE_CHANGE_DIR               // Enable if your spindle controller can change spindle direction
2670 2684
     #define SPINDLE_CHANGE_DIR_STOP            // Enable if the spindle should stop before changing spin direction
2671 2685
     #define SPINDLE_INVERT_DIR          false  // Set to "true" if the spin direction is reversed
2672 2686
 
2687
+    #define SPINDLE_LASER_POWERUP_DELAY   5000 // (ms) Delay to allow the spindle/laser to come up to speed/power
2688
+    #define SPINDLE_LASER_POWERDOWN_DELAY 5000 // (ms) Delay to allow the spindle to stop
2689
+
2673 2690
     /**
2674
-     *  The M3 & M4 commands use the following equation to convert PWM duty cycle to speed/power
2691
+     * M3/M4 uses the following equation to convert speed/power to PWM duty cycle
2692
+     * Power = ((DC / 255 * 100) - SPEED_POWER_INTERCEPT)) * (1 / SPEED_POWER_SLOPE)
2693
+     *   where PWM DC varies from 0 to 255
2675 2694
      *
2676
-     *  SPEED/POWER = PWM duty cycle * SPEED_POWER_SLOPE + SPEED_POWER_INTERCEPT
2677
-     *    where PWM duty cycle varies from 0 to 255
2678
-     *
2679
-     *  set the following for your controller (ALL MUST BE SET)
2695
+     * Set these required parameters for your controller
2680 2696
      */
2681
-    #define SPEED_POWER_SLOPE    118.4
2682
-    #define SPEED_POWER_INTERCEPT  0
2683
-    #define SPEED_POWER_MIN     5000
2684
-    #define SPEED_POWER_MAX    30000    // SuperPID router controller 0 - 30,000 RPM
2697
+    #define SPEED_POWER_SLOPE           118.4  // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255
2698
+    #define SPEED_POWER_INTERCEPT         0
2699
+    #define SPEED_POWER_MIN            5000
2700
+    #define SPEED_POWER_MAX           30000    // SuperPID router controller 0 - 30,000 RPM
2701
+    #define SPEED_POWER_STARTUP       25000    // The default value for speed power when M3 is called without arguments
2702
+
2685 2703
   #else
2686
-    #define SPEED_POWER_SLOPE      0.3922
2687
-    #define SPEED_POWER_INTERCEPT  0
2688
-    #define SPEED_POWER_MIN       10
2689
-    #define SPEED_POWER_MAX      100    // 0-100%
2704
+
2705
+    #define SPEED_POWER_SLOPE             0.3922 // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255
2706
+    #define SPEED_POWER_INTERCEPT         0
2707
+    #define SPEED_POWER_MIN               0
2708
+    #define SPEED_POWER_MAX             100    // 0-100%
2709
+    #define SPEED_POWER_STARTUP          80    // The default value for speed power when M3 is called without arguments
2710
+
2711
+    /**
2712
+     * Enable inline laser power to be handled in the planner / stepper routines.
2713
+     * Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I)
2714
+     * or by the 'S' parameter in G0/G1/G2/G3 moves (see LASER_MOVE_POWER).
2715
+     *
2716
+     * This allows the laser to keep in perfect sync with the planner and removes
2717
+     * the powerup/down delay since lasers require negligible time.
2718
+     */
2719
+    #define LASER_POWER_INLINE
2720
+
2721
+    #if ENABLED(LASER_POWER_INLINE)
2722
+      /**
2723
+       * Scale the laser's power in proportion to the movement rate.
2724
+       *
2725
+       * - Sets the entry power proportional to the entry speed over the nominal speed.
2726
+       * - Ramps the power up every N steps to approximate the speed trapezoid.
2727
+       * - Due to the limited power resolution this is only approximate.
2728
+       */
2729
+      #define LASER_POWER_INLINE_TRAPEZOID
2730
+
2731
+      /**
2732
+       * Continuously calculate the current power (nominal_power * current_rate / nominal_rate).
2733
+       * Required for accurate power with non-trapezoidal acceleration (e.g., S_CURVE_ACCELERATION).
2734
+       * This is a costly calculation so this option is discouraged on 8-bit AVR boards.
2735
+       *
2736
+       * LASER_POWER_INLINE_TRAPEZOID_CONT_PER defines how many step cycles there are between power updates. If your
2737
+       * board isn't able to generate steps fast enough (and you are using LASER_POWER_INLINE_TRAPEZOID_CONT), increase this.
2738
+       * Note that when this is zero it means it occurs every cycle; 1 means a delay wait one cycle then run, etc.
2739
+       */
2740
+      //#define LASER_POWER_INLINE_TRAPEZOID_CONT
2741
+
2742
+      /**
2743
+       * Stepper iterations between power updates. Increase this value if the board
2744
+       * can't keep up with the processing demands of LASER_POWER_INLINE_TRAPEZOID_CONT.
2745
+       * Disable (or set to 0) to recalculate power on every stepper iteration.
2746
+       */
2747
+      //#define LASER_POWER_INLINE_TRAPEZOID_CONT_PER 10
2748
+
2749
+      /**
2750
+       * Include laser power in G0/G1/G2/G3/G5 commands with the 'S' parameter
2751
+       */
2752
+      //#define LASER_MOVE_POWER
2753
+
2754
+      #if ENABLED(LASER_MOVE_POWER)
2755
+        // Turn off the laser on G0 moves with no power parameter.
2756
+        // If a power parameter is provided, use that instead.
2757
+        //#define LASER_MOVE_G0_OFF
2758
+      #endif
2759
+
2760
+      /**
2761
+       * Inline flag inverted
2762
+       *
2763
+       * WARNING: M5 will NOT turn off the laser unless another move
2764
+       *          is done (so G-code files must end with 'M5 I').
2765
+       */
2766
+      //#define LASER_POWER_INLINE_INVERT
2767
+
2768
+      /**
2769
+       * Continuously apply inline power. ('M3 S3' == 'G1 S3' == 'M3 S3 I')
2770
+       *
2771
+       * The laser might do some weird things, so only enable this
2772
+       * feature if you understand the implications.
2773
+       */
2774
+      //#define LASER_POWER_INLINE_CONTINUOUS
2775
+
2776
+    #else
2777
+
2778
+      #define SPINDLE_LASER_POWERUP_DELAY     50 // (ms) Delay to allow the spindle/laser to come up to speed/power
2779
+      #define SPINDLE_LASER_POWERDOWN_DELAY   50 // (ms) Delay to allow the spindle to stop
2780
+
2781
+    #endif
2690 2782
   #endif
2691 2783
 #endif
2692 2784
 

+ 2
- 0
Marlin/src/HAL/AVR/HAL.h View File

@@ -395,6 +395,8 @@ inline void HAL_adc_init() {
395 395
 // AVR compatibility
396 396
 #define strtof strtod
397 397
 
398
+#define HAL_CAN_SET_PWM_FREQ   // This HAL supports PWM Frequency adjustment
399
+
398 400
 /**
399 401
  *  set_pwm_frequency
400 402
  *  Sets the frequency of the timer corresponding to the provided pin

+ 2
- 2
Marlin/src/HAL/AVR/fast_pwm.cpp View File

@@ -23,7 +23,7 @@
23 23
 
24 24
 #include "../../inc/MarlinConfigPre.h"
25 25
 
26
-#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM
26
+#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
27 27
 
28 28
 #include "HAL.h"
29 29
 
@@ -278,5 +278,5 @@ void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255
278 278
   }
279 279
 }
280 280
 
281
-#endif // FAST_PWM_FAN || SPINDLE_LASER_PWM
281
+#endif // NEEDS_HARDWARE_PWM
282 282
 #endif // __AVR__

+ 2
- 0
Marlin/src/HAL/LPC1768/HAL.h View File

@@ -197,6 +197,8 @@ void HAL_idletask();
197 197
 #define PLATFORM_M997_SUPPORT
198 198
 void flashFirmware(const int16_t);
199 199
 
200
+#define HAL_CAN_SET_PWM_FREQ   // This HAL supports PWM Frequency adjustment
201
+
200 202
 /**
201 203
  * set_pwm_frequency
202 204
  *  Set the frequency of the timer corresponding to the provided pin

+ 1
- 1
Marlin/src/HAL/LPC1768/fast_pwm.cpp View File

@@ -24,7 +24,7 @@
24 24
 
25 25
 #include "../../inc/MarlinConfigPre.h"
26 26
 
27
-#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM
27
+#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
28 28
 
29 29
 #include <pwm.h>
30 30
 

+ 17
- 5
Marlin/src/MarlinCore.cpp View File

@@ -420,7 +420,11 @@ void startOrResumeJob() {
420 420
     #if DISABLED(SD_ABORT_NO_COOLDOWN)
421 421
       thermalManager.disable_all_heaters();
422 422
     #endif
423
-    thermalManager.zero_fan_speeds();
423
+    #if !HAS_CUTTER
424
+      thermalManager.zero_fan_speeds();
425
+    #else
426
+      cutter.kill();              // Full cutter shutdown including ISR control
427
+    #endif
424 428
     wait_for_heatup = false;
425 429
     #if ENABLED(POWER_LOSS_RECOVERY)
426 430
       recovery.purge();
@@ -741,6 +745,10 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) {
741 745
 void kill(PGM_P const lcd_error/*=nullptr*/, PGM_P const lcd_component/*=nullptr*/, const bool steppers_off/*=false*/) {
742 746
   thermalManager.disable_all_heaters();
743 747
 
748
+  #if HAS_CUTTER
749
+    cutter.kill();              // Full cutter shutdown including ISR control
750
+  #endif
751
+
744 752
   SERIAL_ERROR_MSG(STR_ERR_KILLED);
745 753
 
746 754
   #if HAS_DISPLAY
@@ -770,6 +778,10 @@ void minkill(const bool steppers_off/*=false*/) {
770 778
   // Reiterate heaters off
771 779
   thermalManager.disable_all_heaters();
772 780
 
781
+  #if HAS_CUTTER
782
+    cutter.kill();  // Reiterate cutter shutdown
783
+  #endif
784
+
773 785
   // Power off all steppers (for M112) or just the E steppers
774 786
   steppers_off ? disable_all_steppers() : disable_e_steppers();
775 787
 
@@ -789,14 +801,14 @@ void minkill(const bool steppers_off/*=false*/) {
789 801
     // Wait for kill to be pressed
790 802
     while (READ(KILL_PIN)) watchdog_refresh();
791 803
 
792
-    void (*resetFunc)() = 0;  // Declare resetFunc() at address 0
804
+    void (*resetFunc)() = 0;      // Declare resetFunc() at address 0
793 805
     resetFunc();                  // Jump to address 0
794 806
 
795
-  #else // !HAS_KILL
807
+  #else
796 808
 
797
-    for (;;) watchdog_refresh(); // Wait for reset
809
+    for (;;) watchdog_refresh();  // Wait for reset
798 810
 
799
-  #endif // !HAS_KILL
811
+  #endif
800 812
 }
801 813
 
802 814
 /**

+ 0
- 9
Marlin/src/core/drivers.h View File

@@ -174,15 +174,6 @@
174 174
 // Defines that can't be evaluated now
175 175
 #define HAS_TMC_SW_SERIAL ANY_AXIS_HAS(SW_SERIAL)
176 176
 
177
-//
178
-// Stretching 'drivers.h' to include LPC/SAMD51 SD options
179
-//
180
-#define _SDCARD_LCD          1
181
-#define _SDCARD_ONBOARD      2
182
-#define _SDCARD_CUSTOM_CABLE 3
183
-#define _SDCARD_ID(V) _CAT(_SDCARD_, V)
184
-#define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V))
185
-
186 177
 #if HAS_DRIVER(L6470) || HAS_DRIVER(L6474) || HAS_DRIVER(L6480) || HAS_DRIVER(POWERSTEP01)
187 178
   #define HAS_L64XX 1
188 179
 #endif

+ 29
- 20
Marlin/src/feature/spindle_laser.cpp View File

@@ -32,10 +32,17 @@
32 32
 
33 33
 SpindleLaser cutter;
34 34
 
35
-cutter_power_t SpindleLaser::power; // = 0
36
-
35
+cutter_power_t SpindleLaser::power;
36
+bool SpindleLaser::isOn;                                                       // state to determine when to apply setPower to power
37
+cutter_setPower_t SpindleLaser::setPower = interpret_power(SPEED_POWER_MIN);   // spindle/laser speed/power control in PWM, Percentage or RPM
38
+#if ENABLED(MARLIN_DEV_MODE)
39
+  cutter_frequency_t SpindleLaser::frequency;                                  // setting PWM frequency; range: 2K - 50K
40
+#endif
37 41
 #define SPINDLE_LASER_PWM_OFF ((SPINDLE_LASER_PWM_INVERT) ? 255 : 0)
38 42
 
43
+//
44
+// Init the cutter to a safe OFF state
45
+//
39 46
 void SpindleLaser::init() {
40 47
   OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Init spindle to off
41 48
   #if ENABLED(SPINDLE_CHANGE_DIR)
@@ -45,41 +52,39 @@ void SpindleLaser::init() {
45 52
     SET_PWM(SPINDLE_LASER_PWM_PIN);
46 53
     analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF);  // set to lowest speed
47 54
   #endif
55
+  #if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
56
+    set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
57
+    #if ENABLED(MARLIN_DEV_MODE)
58
+      frequency = SPINDLE_LASER_FREQUENCY;
59
+    #endif
60
+  #endif
48 61
 }
49 62
 
50 63
 #if ENABLED(SPINDLE_LASER_PWM)
51 64
 
52 65
   /**
53
-   * ocr_val_mode() is used for debugging and to get the points needed to compute the RPM vs ocr_val line
54
-   *
55
-   * it accepts inputs of 0-255
56
-   */
66
+  * Set the cutter PWM directly to the given ocr value
67
+  **/
57 68
   void SpindleLaser::set_ocr(const uint8_t ocr) {
58
-    WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on (active low)
69
+    WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on
59 70
     analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
60 71
   }
61 72
 
62 73
 #endif
63 74
 
75
+//
76
+// Set cutter ON state (and PWM) to the given cutter power value
77
+//
64 78
 void SpindleLaser::apply_power(const cutter_power_t inpow) {
65 79
   static cutter_power_t last_power_applied = 0;
66 80
   if (inpow == last_power_applied) return;
67 81
   last_power_applied = inpow;
68 82
   #if ENABLED(SPINDLE_LASER_PWM)
69
-    if (enabled()) {
70
-      #define _scaled(F) ((F - (SPEED_POWER_INTERCEPT)) * inv_slope)
71
-      constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE),
72
-                      min_ocr = _scaled(SPEED_POWER_MIN),
73
-                      max_ocr = _scaled(SPEED_POWER_MAX);
74
-      int16_t ocr_val;
75
-           if (inpow <= SPEED_POWER_MIN) ocr_val = min_ocr;       // Use minimum if set below
76
-      else if (inpow >= SPEED_POWER_MAX) ocr_val = max_ocr;       // Use maximum if set above
77
-      else ocr_val = _scaled(inpow);                              // Use calculated OCR value
78
-      set_ocr(ocr_val & 0xFF);                                    // ...limited to Atmel PWM max
79
-    }
83
+    if (enabled())
84
+      set_ocr(translate_power(inpow));
80 85
     else {
81
-      WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH);   // Turn spindle off (active low)
82
-      analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF);  // Only write low byte
86
+      WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH);                           // Turn spindle off
87
+      analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF);                   // Only write low byte
83 88
     }
84 89
   #else
85 90
     WRITE(SPINDLE_LASER_ENA_PIN, (SPINDLE_LASER_ACTIVE_HIGH) ? enabled() : !enabled());
@@ -88,6 +93,10 @@ void SpindleLaser::apply_power(const cutter_power_t inpow) {
88 93
 
89 94
 #if ENABLED(SPINDLE_CHANGE_DIR)
90 95
 
96
+  //
97
+  // Set the spindle direction and apply immediately
98
+  // Stop on direction change if SPINDLE_STOP_ON_DIR_CHANGE is enabled
99
+  //
91 100
   void SpindleLaser::set_direction(const bool reverse) {
92 101
     const bool dir_state = (reverse == SPINDLE_INVERT_DIR); // Forward (M3) HIGH when not inverted
93 102
     #if ENABLED(SPINDLE_STOP_ON_DIR_CHANGE)

+ 110
- 33
Marlin/src/feature/spindle_laser.h View File

@@ -28,55 +28,98 @@
28 28
 
29 29
 #include "../inc/MarlinConfig.h"
30 30
 
31
-#if ENABLED(SPINDLE_FEATURE)
32
-  #define _MSG_CUTTER(M) MSG_SPINDLE_##M
33
-#else
34
-  #define _MSG_CUTTER(M) MSG_LASER_##M
35
-#endif
36
-#define MSG_CUTTER(M) _MSG_CUTTER(M)
37
-
38
-#if SPEED_POWER_MAX > 255
39
-  typedef uint16_t cutter_power_t;
40
-  #define CUTTER_MENU_TYPE uint16_5
41
-#else
42
-  typedef uint8_t cutter_power_t;
43
-  #define CUTTER_MENU_TYPE uint8
31
+#include "spindle_laser_types.h"
32
+
33
+#if ENABLED(LASER_POWER_INLINE)
34
+  #include "../module/planner.h"
44 35
 #endif
45 36
 
46 37
 class SpindleLaser {
47 38
 public:
39
+  static bool isOn;                             //  state to determine when to apply setPower to power
48 40
   static cutter_power_t power;
49
-  static inline uint8_t powerPercent(const uint8_t pp) { return ui8_to_percent(pp); } // for display
50
-
51
-  static void init();
52
-
53
-  static inline bool enabled() { return !!power; }
41
+  static cutter_setPower_t setPower;            //  spindle/laser menu set power; in PWM, Percentage or RPM
42
+  #if ENABLED(MARLIN_DEV_MODE)
43
+    static cutter_frequency_t frequency;        //  set PWM frequency; range: 2K-50K
44
+  #endif
54 45
 
55
-  static inline void set_power(const cutter_power_t pwr) { power = pwr; }
46
+  static cutter_setPower_t interpret_power(const float pwr) {     // convert speed/power to configured PWM, Percentage or RPM in relative or normal range
47
+    #if CUTTER_DISPLAY_IS(PERCENT)
48
+      return (pwr / SPEED_POWER_MAX) * 100;                                               // to percent
49
+    #elif CUTTER_DISPLAY_IS(RPM)                                                          // to RPM is unaltered
50
+      return pwr;
51
+    #else                                                                                 // to PWM
52
+      #if ENABLED(CUTTER_POWER_RELATIVE)
53
+        return (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 255;     // using rpm range as relative percentage
54
+      #else
55
+        return (pwr / SPEED_POWER_MAX) * 255;
56
+      #endif
57
+    #endif
58
+  }
59
+  /**
60
+  * Translate speed/power --> percentage --> PWM value
61
+  **/
62
+  static cutter_power_t translate_power(const float pwr) {
63
+    float pwrpc;
64
+    #if CUTTER_DISPLAY_IS(PERCENT)
65
+      pwrpc = pwr;
66
+    #elif CUTTER_DISPLAY_IS(RPM)            // RPM to percent
67
+     #if ENABLED(CUTTER_POWER_RELATIVE)
68
+        pwrpc = (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 100;
69
+      #else
70
+        pwrpc = pwr / SPEED_POWER_MAX * 100;
71
+      #endif
72
+    #else
73
+      return pwr;                           // PWM
74
+    #endif
56 75
 
57
-  static inline void refresh() { apply_power(power); }
76
+    #if ENABLED(SPINDLE_FEATURE)
77
+      #if ENABLED(CUTTER_POWER_RELATIVE)
78
+        constexpr float spmin = 0;
79
+      #else
80
+        constexpr float spmin = SPEED_POWER_MIN / SPEED_POWER_MAX * 100; // convert to percentage
81
+      #endif
82
+      constexpr float spmax = 100;
83
+    #else
84
+      constexpr float spmin = SPEED_POWER_MIN;
85
+      constexpr float spmax = SPEED_POWER_MAX;
86
+    #endif
58 87
 
59
-  static inline void set_enabled(const bool enable) {
60
-    const bool was = enabled();
61
-    set_power(enable ? 255 : 0);
62
-    if (was != enable) power_delay();
88
+    constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE),
89
+                    min_ocr = (spmin - (SPEED_POWER_INTERCEPT)) * inv_slope,         // Minimum allowed
90
+                    max_ocr = (spmax - (SPEED_POWER_INTERCEPT)) * inv_slope;         // Maximum allowed
91
+    float ocr_val;
92
+    if (pwrpc < spmin) ocr_val = min_ocr;                                           // Use minimum if set below
93
+    else if (pwrpc > spmax) ocr_val = max_ocr;                                      // Use maximum if set above
94
+    else ocr_val = (pwrpc - (SPEED_POWER_INTERCEPT)) * inv_slope;                   // Use calculated OCR value
95
+    return ocr_val;                                                                 // ...limited to Atmel PWM max
63 96
   }
64 97
 
98
+  static void init();
99
+
100
+  // Modifying this function should update everywhere
101
+  static inline bool enabled(const cutter_power_t pwr) { return pwr > 0; }
102
+  static inline bool enabled() { return enabled(power); }
103
+  #if ENABLED(MARLIN_DEV_MODE)
104
+    static inline void refresh_frequency() { set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
105
+  #endif
65 106
   static void apply_power(const cutter_power_t inpow);
66 107
 
67
-  //static bool active() { return READ(SPINDLE_LASER_ENA_PIN) == SPINDLE_LASER_ACTIVE_HIGH; }
108
+  FORCE_INLINE static void refresh() { apply_power(power); }
109
+  FORCE_INLINE static void set_power(const cutter_power_t pwr) { power = pwr; refresh(); }
68 110
 
69
-  static void update_output();
111
+  static inline void set_enabled(const bool enable) { set_power(enable ? (power ?: interpret_power(SPEED_POWER_STARTUP)) : 0); }
70 112
 
71 113
   #if ENABLED(SPINDLE_LASER_PWM)
72 114
     static void set_ocr(const uint8_t ocr);
73
-    static inline void set_ocr_power(const cutter_power_t pwr) { power = pwr; set_ocr(pwr); }
115
+    static inline void set_ocr_power(const uint8_t pwr) { power = pwr; set_ocr(pwr); }
116
+    // static uint8_t translate_power(const cutter_power_t pwr); // Used by update output for power->OCR translation
74 117
   #endif
75 118
 
76 119
   // Wait for spindle to spin up or spin down
77
-  static inline void power_delay() {
78
-    #if SPINDLE_LASER_POWERUP_DELAY || SPINDLE_LASER_POWERDOWN_DELAY
79
-      safe_delay(enabled() ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
120
+  static inline void power_delay(const bool on) {
121
+    #if DISABLED(LASER_POWER_INLINE)
122
+      safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
80 123
     #endif
81 124
   }
82 125
 
@@ -86,10 +129,44 @@ public:
86 129
     static inline void set_direction(const bool) {}
87 130
   #endif
88 131
 
89
-  static inline void disable() { set_enabled(false); }
90
-  static inline void enable_forward() { set_direction(false); set_enabled(true); }
91
-  static inline void enable_reverse() { set_direction(true); set_enabled(true); }
132
+  static inline void disable() { isOn = false; set_enabled(false); }
133
+  #if HAS_LCD_MENU
134
+    static inline void enable_forward() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(false); set_enabled(true); }
135
+    static inline void enable_reverse() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(true); set_enabled(true); }
136
+  #endif
92 137
 
138
+  #if ENABLED(LASER_POWER_INLINE)
139
+    // Force disengage planner power control
140
+    static inline void inline_disable() { planner.settings.laser.status = 0; planner.settings.laser.power = 0; isOn = false;}
141
+
142
+    // Inline modes of all other functions; all enable planner inline power control
143
+    static inline void inline_enabled(const bool enable) { enable ? inline_power(SPEED_POWER_STARTUP) : inline_ocr_power(0); }
144
+
145
+    static void inline_power(const cutter_power_t pwr) {
146
+      #if ENABLED(SPINDLE_LASER_PWM)
147
+        inline_ocr_power(translate_power(pwr));
148
+      #else
149
+        planner.settings.laser.status = enabled(pwr) ? 0x03 : 0x01;
150
+        planner.settings.laser.power = pwr;
151
+      #endif
152
+    }
153
+
154
+    static inline void inline_direction(const bool reverse) { UNUSED(reverse); } // TODO is this ever going to be needed
155
+
156
+    #if ENABLED(SPINDLE_LASER_PWM)
157
+      static inline void inline_ocr_power(const uint8_t pwr) {
158
+        planner.settings.laser.status = pwr ? 0x03 : 0x01;
159
+        planner.settings.laser.power = pwr;
160
+      }
161
+    #endif
162
+  #endif
163
+
164
+  static inline void kill() {
165
+    #if ENABLED(LASER_POWER_INLINE)
166
+      inline_disable();
167
+    #endif
168
+    disable();
169
+  }
93 170
 };
94 171
 
95 172
 extern SpindleLaser cutter;

+ 50
- 0
Marlin/src/feature/spindle_laser_types.h View File

@@ -0,0 +1,50 @@
1
+/**
2
+ * Marlin 3D Printer Firmware
3
+ * Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
4
+ *
5
+ * Based on Sprinter and grbl.
6
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
7
+ *
8
+ * This program is free software: you can redistribute it and/or modify
9
+ * it under the terms of the GNU General Public License as published by
10
+ * the Free Software Foundation, either version 3 of the License, or
11
+ * (at your option) any later version.
12
+ *
13
+ * This program is distributed in the hope that it will be useful,
14
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ * GNU General Public License for more details.
17
+ *
18
+ * You should have received a copy of the GNU General Public License
19
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
+ *
21
+ */
22
+#pragma once
23
+
24
+/**
25
+ * feature/spindle_laser_types.h
26
+ * Support for Laser Power or Spindle Power & Direction
27
+ */
28
+
29
+#include "../inc/MarlinConfigPre.h"
30
+
31
+#if ENABLED(SPINDLE_FEATURE)
32
+  #define _MSG_CUTTER(M) MSG_SPINDLE_##M
33
+#else
34
+  #define _MSG_CUTTER(M) MSG_LASER_##M
35
+#endif
36
+#define MSG_CUTTER(M) _MSG_CUTTER(M)
37
+#if CUTTER_DISPLAY_IS(RPM) && SPEED_POWER_MAX > 255
38
+  #define cutter_power_t              uint16_t
39
+  #define cutter_setPower_t           uint16_t
40
+  #define CUTTER_MENU_POWER_TYPE      uint16_5
41
+#else
42
+  #define cutter_power_t              uint8_t
43
+  #define cutter_setPower_t           uint8_t
44
+  #define CUTTER_MENU_POWER_TYPE      uint8
45
+#endif
46
+
47
+#if ENABLED(MARLIN_DEV_MODE)
48
+  #define cutter_frequency_t          uint16_t
49
+  #define CUTTER_MENU_FREQUENCY_TYPE  uint16_5
50
+#endif

+ 35
- 6
Marlin/src/gcode/control/M3-M5.cpp View File

@@ -28,6 +28,12 @@
28 28
 #include "../../feature/spindle_laser.h"
29 29
 #include "../../module/stepper.h"
30 30
 
31
+inline cutter_power_t get_s_power() {
32
+  return cutter_power_t(
33
+    parser.intval('S', cutter.interpret_power(SPEED_POWER_STARTUP))
34
+  );
35
+}
36
+
31 37
 /**
32 38
  * Laser:
33 39
  *
@@ -71,29 +77,52 @@
71 77
  */
72 78
 void GcodeSuite::M3_M4(const bool is_M4) {
73 79
 
74
-  #if ENABLED(SPINDLE_FEATURE)
75
-    planner.synchronize();   // Wait for movement to complete before changing power
80
+  #if ENABLED(LASER_POWER_INLINE)
81
+    if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
82
+      // Laser power in inline mode
83
+      cutter.inline_direction(is_M4); // Should always be unused
84
+
85
+      #if ENABLED(SPINDLE_LASER_PWM)
86
+        if (parser.seen('O'))
87
+          cutter.inline_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t)
88
+        else
89
+          cutter.inline_power(get_s_power());
90
+      #else
91
+        cutter.inline_enabled(true);
92
+      #endif
93
+      return;
94
+    }
95
+    // Non-inline, standard case
96
+    cutter.inline_disable(); // Prevent future blocks re-setting the power
76 97
   #endif
77 98
 
99
+  planner.synchronize();   // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power
100
+
78 101
   cutter.set_direction(is_M4);
79 102
 
80 103
   #if ENABLED(SPINDLE_LASER_PWM)
81 104
     if (parser.seenval('O'))
82 105
       cutter.set_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t)
83 106
     else
84
-      cutter.set_power(parser.intval('S', 255));
107
+      cutter.set_power(get_s_power());
85 108
   #else
86 109
     cutter.set_enabled(true);
87 110
   #endif
88 111
 }
89 112
 
90 113
 /**
91
- * M5 - Cutter OFF
114
+ * M5 - Cutter OFF (when moves are complete)
92 115
  */
93 116
 void GcodeSuite::M5() {
94
-  #if ENABLED(SPINDLE_FEATURE)
95
-    planner.synchronize();
117
+  #if ENABLED(LASER_POWER_INLINE)
118
+    if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
119
+      cutter.inline_enabled(false); // Laser power in inline mode
120
+      return;
121
+    }
122
+    // Non-inline, standard case
123
+    cutter.inline_disable(); // Prevent future blocks re-setting the power
96 124
   #endif
125
+  planner.synchronize();
97 126
   cutter.set_enabled(false);
98 127
 }
99 128
 

+ 16
- 0
Marlin/src/gcode/gcode.cpp View File

@@ -53,6 +53,10 @@ GcodeSuite gcode;
53 53
   #include "../feature/cancel_object.h"
54 54
 #endif
55 55
 
56
+#if ENABLED(LASER_MOVE_POWER)
57
+  #include "../feature/spindle_laser.h"
58
+#endif
59
+
56 60
 #include "../MarlinCore.h" // for idle()
57 61
 
58 62
 millis_t GcodeSuite::previous_move_ms;
@@ -172,6 +176,18 @@ void GcodeSuite::get_destination_from_command() {
172 176
   #if BOTH(MIXING_EXTRUDER, DIRECT_MIXING_IN_G1)
173 177
     M165();
174 178
   #endif
179
+
180
+  #if ENABLED(LASER_MOVE_POWER)
181
+    // Set the laser power in the planner to configure this move
182
+    if (parser.seen('S'))
183
+      cutter.inline_power(parser.value_int());
184
+    else {
185
+      #if ENABLED(LASER_MOVE_G0_OFF)
186
+        if (parser.codenum == 0)        // G0
187
+          cutter.inline_enabled(false);
188
+      #endif
189
+    }
190
+  #endif
175 191
 }
176 192
 
177 193
 /**

+ 1
- 1
Marlin/src/gcode/motion/G0_G1.cpp View File

@@ -69,7 +69,7 @@ void GcodeSuite::G0_G1(
69 69
       #endif
70 70
     #endif
71 71
 
72
-    get_destination_from_command();                 // Process X Y Z E F parameters
72
+    get_destination_from_command();                 // Get X Y Z E F (and set cutter power)
73 73
 
74 74
     #ifdef G0_FEEDRATE
75 75
       if (fast_move) {

+ 1
- 1
Marlin/src/gcode/motion/G2_G3.cpp View File

@@ -283,7 +283,7 @@ void GcodeSuite::G2_G3(const bool clockwise) {
283 283
       relative_mode = true;
284 284
     #endif
285 285
 
286
-    get_destination_from_command();
286
+    get_destination_from_command();   // Get X Y Z E F (and set cutter power)
287 287
 
288 288
     #if ENABLED(SF_ARC_FIX)
289 289
       relative_mode = relative_mode_backup;

+ 31
- 1
Marlin/src/inc/Conditionals_adv.h View File

@@ -116,7 +116,23 @@
116 116
   #define Z_STEPPER_ALIGN_AMP 1.0
117 117
 #endif
118 118
 
119
-#define HAS_CUTTER EITHER(SPINDLE_FEATURE, LASER_FEATURE)
119
+//
120
+// Spindle/Laser power display types
121
+// Defined here so sanity checks can use them
122
+//
123
+#if EITHER(SPINDLE_FEATURE, LASER_FEATURE)
124
+  #define HAS_CUTTER 1
125
+  #define _CUTTER_DISP_PWM     1
126
+  #define _CUTTER_DISP_PERCENT 2
127
+  #define _CUTTER_DISP_RPM     3
128
+  #define _CUTTER_DISP(V)      _CAT(_CUTTER_DISP_, V)
129
+  #define CUTTER_DISPLAY_IS(V) (_CUTTER_DISP(CUTTER_POWER_DISPLAY) == _CUTTER_DISP(V))
130
+#endif
131
+
132
+// Add features that need hardware PWM here
133
+#if ANY(FAST_PWM_FAN, SPINDLE_LASER_PWM)
134
+  #define NEEDS_HARDWARE_PWM 1
135
+#endif
120 136
 
121 137
 #if !defined(__AVR__) || !defined(USBCON)
122 138
   // Define constants and variables for buffering serial data.
@@ -290,3 +306,17 @@
290 306
     #define MAXIMUM_STEPPER_RATE 250000
291 307
   #endif
292 308
 #endif
309
+
310
+//
311
+// SD Card connection methods
312
+// Defined here so pins and sanity checks can use them
313
+//
314
+#if ENABLED(SDSUPPORT)
315
+  #define _SDCARD_LCD          1
316
+  #define _SDCARD_ONBOARD      2
317
+  #define _SDCARD_CUSTOM_CABLE 3
318
+  #define _SDCARD_ID(V) _CAT(_SDCARD_, V)
319
+  #define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V))
320
+#else
321
+  #define SD_CONNECTION_IS(...) 0
322
+#endif

+ 26
- 22
Marlin/src/inc/Conditionals_post.h View File

@@ -324,17 +324,36 @@
324 324
 
325 325
 /**
326 326
  * Override the SD_DETECT_STATE set in Configuration_adv.h
327
+ * and enable sharing of onboard SD host drives (all platforms but AGCM4)
327 328
  */
328 329
 #if ENABLED(SDSUPPORT)
329
-  #if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION))
330
-    #undef SD_DETECT_STATE
331
-    #if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
332
-      #define SD_DETECT_STATE HIGH
333
-    #endif
330
+
331
+  #if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4)
332
+    //
333
+    // The external SD card is not used. Hardware SPI is used to access the card.
334
+    // When sharing the SD card with a PC we want the menu options to
335
+    // mount/unmount the card and refresh it. So we disable card detect.
336
+    //
337
+    #undef SD_DETECT_PIN
338
+    #define SHARED_SD_CARD
339
+  #endif
340
+
341
+  #if DISABLED(SHARED_SD_CARD)
342
+    #define INIT_SDCARD_ON_BOOT 1
334 343
   #endif
335
-  #ifndef SD_DETECT_STATE
336
-    #define SD_DETECT_STATE LOW
344
+
345
+  #if PIN_EXISTS(SD_DETECT)
346
+    #if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION))
347
+      #undef SD_DETECT_STATE
348
+      #if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
349
+        #define SD_DETECT_STATE HIGH
350
+      #endif
351
+    #endif
352
+    #ifndef SD_DETECT_STATE
353
+      #define SD_DETECT_STATE LOW
354
+    #endif
337 355
   #endif
356
+
338 357
 #endif
339 358
 
340 359
 /**
@@ -2153,21 +2172,6 @@
2153 2172
   #endif
2154 2173
 #endif
2155 2174
 
2156
-#if ENABLED(SDSUPPORT)
2157
-  #if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4)
2158
-    //
2159
-    // The external SD card is not used. Hardware SPI is used to access the card.
2160
-    // When sharing the SD card with a PC we want the menu options to
2161
-    // mount/unmount the card and refresh it. So we disable card detect.
2162
-    //
2163
-    #undef SD_DETECT_PIN
2164
-    #define SHARED_SD_CARD
2165
-  #endif
2166
-  #if DISABLED(SHARED_SD_CARD)
2167
-    #define INIT_SDCARD_ON_BOOT 1
2168
-  #endif
2169
-#endif
2170
-
2171 2175
 #if !NUM_SERIAL
2172 2176
   #undef BAUD_RATE_GCODE
2173 2177
 #endif

+ 43
- 8
Marlin/src/inc/SanityCheck.h View File

@@ -1451,7 +1451,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
1451 1451
  * Deploying the Allen Key probe uses big moves in z direction. Too dangerous for an unhomed z-axis.
1452 1452
  */
1453 1453
 #if ENABLED(Z_PROBE_ALLEN_KEY) && (Z_HOME_DIR < 0) && ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
1454
-  #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY"
1454
+  #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY."
1455 1455
 #endif
1456 1456
 
1457 1457
 /**
@@ -2654,9 +2654,9 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
2654 2654
 
2655 2655
 #if ENABLED(BACKLASH_COMPENSATION)
2656 2656
   #ifndef BACKLASH_DISTANCE_MM
2657
-    #error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM"
2657
+    #error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM."
2658 2658
   #elif !defined(BACKLASH_CORRECTION)
2659
-    #error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION"
2659
+    #error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION."
2660 2660
   #elif IS_CORE
2661 2661
     constexpr float backlash_arr[] = BACKLASH_DISTANCE_MM;
2662 2662
     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)
2736 2736
 #endif
2737 2737
 
2738 2738
 #if HAS_CUTTER
2739
+  #ifndef CUTTER_POWER_DISPLAY
2740
+    #error "CUTTER_POWER_DISPLAY is required with a spindle or laser. Please update your Configuration_adv.h."
2741
+  #elif !CUTTER_DISPLAY_IS(PWM) && !CUTTER_DISPLAY_IS(PERCENT) && !CUTTER_DISPLAY_IS(RPM)
2742
+    #error "CUTTER_POWER_DISPLAY must be PWM, PERCENT, or RPM. Please update your Configuration_adv.h."
2743
+  #endif
2744
+
2745
+  #if ENABLED(LASER_POWER_INLINE)
2746
+    #if ENABLED(SPINDLE_CHANGE_DIR)
2747
+      #error "SPINDLE_CHANGE_DIR and LASER_POWER_INLINE are incompatible."
2748
+    #elif ENABLED(LASER_MOVE_G0_OFF) && DISABLED(LASER_MOVE_POWER)
2749
+      #error "LASER_MOVE_G0_OFF requires LASER_MOVE_POWER. Please update your Configuration_adv.h."
2750
+    #endif
2751
+    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
2752
+      #if DISABLED(SPINDLE_LASER_PWM)
2753
+        #error "LASER_POWER_INLINE_TRAPEZOID requires SPINDLE_LASER_PWM to function."
2754
+      #elif ENABLED(S_CURVE_ACCELERATION)
2755
+        //#ifndef LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
2756
+        //  #define LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
2757
+        //  #warning "Combining LASER_POWER_INLINE_TRAPEZOID with S_CURVE_ACCELERATION may result in unintended behavior."
2758
+        //#endif
2759
+      #endif
2760
+    #endif
2761
+    #if ENABLED(LASER_POWER_INLINE_INVERT)
2762
+      //#ifndef LASER_POWER_INLINE_INVERT_WARN
2763
+      //  #define LASER_POWER_INLINE_INVERT_WARN
2764
+      //  #warning "Enabling LASER_POWER_INLINE_INVERT means that `M5` won't kill the laser immediately; use `M5 I` instead."
2765
+      //#endif
2766
+    #endif
2767
+  #else
2768
+    #if SPINDLE_LASER_POWERUP_DELAY < 1
2769
+      #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
2770
+    #elif SPINDLE_LASER_POWERDOWN_DELAY < 1
2771
+      #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
2772
+    #elif ENABLED(LASER_MOVE_POWER)
2773
+      #error "LASER_MOVE_POWER requires LASER_POWER_INLINE."
2774
+    #elif ANY(LASER_POWER_INLINE_TRAPEZOID, LASER_POWER_INLINE_INVERT, LASER_MOVE_G0_OFF, LASER_MOVE_POWER)
2775
+      #error "Enabled an inline laser feature without inline laser power being enabled."
2776
+    #endif
2777
+  #endif
2739 2778
   #define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN)
2740 2779
   #if BOTH(SPINDLE_FEATURE, LASER_FEATURE)
2741 2780
     #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)
2748 2787
       #error "SPINDLE_LASER_PWM_PIN is required for SPINDLE_LASER_PWM."
2749 2788
     #elif !PWM_PIN(SPINDLE_LASER_PWM_PIN)
2750 2789
       #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
2751
-    #elif SPINDLE_LASER_POWERUP_DELAY < 1
2752
-      #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
2753
-    #elif SPINDLE_LASER_POWERDOWN_DELAY < 1
2754
-      #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
2755 2790
     #elif !defined(SPINDLE_LASER_PWM_INVERT)
2756 2791
       #error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE."
2757
-    #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX)
2792
+    #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX) || !defined(SPEED_POWER_STARTUP)
2758 2793
       #error "SPINDLE_LASER_PWM equation constant(s) missing."
2759 2794
     #elif _PIN_CONFLICT(X_MIN)
2760 2795
       #error "SPINDLE_LASER_PWM pin conflicts with X_MIN_PIN."

+ 6
- 2
Marlin/src/lcd/dogm/status_screen_DOGM.cpp View File

@@ -570,8 +570,12 @@ void MarlinUI::draw_status_screen() {
570 570
     // Laser / Spindle
571 571
     #if DO_DRAW_CUTTER
572 572
       if (cutter.power && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
573
-        lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.powerPercent(cutter.power)));
574
-        lcd_put_wchar('%');
573
+        lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.power));
574
+        #if CUTTER_DISPLAY_IS(PERCENT)
575
+          lcd_put_wchar('%');
576
+        #elif CUTTER_DISPLAY_IS(RPM)
577
+          lcd_put_wchar('K');
578
+        #endif
575 579
       }
576 580
     #endif
577 581
 

+ 2
- 1
Marlin/src/lcd/language/language_en.h View File

@@ -90,6 +90,7 @@ namespace Language_en {
90 90
   PROGMEM Language_Str MSG_PREHEAT_2_SETTINGS              = _UxGT("Preheat ") PREHEAT_2_LABEL _UxGT(" Conf");
91 91
   PROGMEM Language_Str MSG_PREHEAT_CUSTOM                  = _UxGT("Preheat Custom");
92 92
   PROGMEM Language_Str MSG_COOLDOWN                        = _UxGT("Cooldown");
93
+  PROGMEM Language_Str MSG_CUTTER_FREQUENCY                = _UxGT("Frequency");
93 94
   PROGMEM Language_Str MSG_LASER_MENU                      = _UxGT("Laser Control");
94 95
   PROGMEM Language_Str MSG_LASER_OFF                       = _UxGT("Laser Off");
95 96
   PROGMEM Language_Str MSG_LASER_ON                        = _UxGT("Laser On");
@@ -603,7 +604,7 @@ namespace Language_en {
603 604
   PROGMEM Language_Str MSG_BACKLASH_C                      = LCD_STR_C;
604 605
   PROGMEM Language_Str MSG_BACKLASH_CORRECTION             = _UxGT("Correction");
605 606
   PROGMEM Language_Str MSG_BACKLASH_SMOOTHING              = _UxGT("Smoothing");
606
-  
607
+
607 608
   PROGMEM Language_Str MSG_LEVEL_X_AXIS                    = _UxGT("Level X Axis");
608 609
   PROGMEM Language_Str MSG_AUTO_CALIBRATE                  = _UxGT("Auto Calibrate");
609 610
   PROGMEM Language_Str MSG_HEATER_TIMEOUT                  = _UxGT("Heater Timeout");

+ 16
- 5
Marlin/src/lcd/menu/menu_spindle_laser.cpp View File

@@ -36,18 +36,29 @@
36 36
 
37 37
     START_MENU();
38 38
     BACK_ITEM(MSG_MAIN);
39
-    if (cutter.enabled()) {
40
-      #if ENABLED(SPINDLE_LASER_PWM)
41
-        EDIT_ITEM(CUTTER_MENU_TYPE, MSG_CUTTER(POWER), &cutter.power, SPEED_POWER_MIN, SPEED_POWER_MAX);
42
-      #endif
39
+    #if ENABLED(SPINDLE_LASER_PWM)
40
+      EDIT_ITEM_FAST(CUTTER_MENU_POWER_TYPE, MSG_CUTTER(POWER), &cutter.setPower, cutter.interpret_power(SPEED_POWER_MIN), cutter.interpret_power(SPEED_POWER_MAX),
41
+      []{
42
+        if (cutter.isOn) {
43
+          cutter.power = cutter.setPower;
44
+        }
45
+      });
46
+    #endif
47
+
48
+    if (cutter.enabled() && cutter.isOn)
43 49
       ACTION_ITEM(MSG_CUTTER(OFF), cutter.disable);
44
-    }
45 50
     else {
46 51
       ACTION_ITEM(MSG_CUTTER(ON), cutter.enable_forward);
47 52
       #if ENABLED(SPINDLE_CHANGE_DIR)
48 53
         ACTION_ITEM(MSG_SPINDLE_REVERSE, cutter.enable_reverse);
49 54
       #endif
50 55
     }
56
+
57
+    #if ENABLED(MARLIN_DEV_MODE)
58
+      #if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
59
+        EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 50000,[]{ cutter.refresh_frequency();});
60
+      #endif
61
+    #endif
51 62
     END_MENU();
52 63
   }
53 64
 

+ 50
- 4
Marlin/src/module/planner.cpp View File

@@ -815,11 +815,10 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
815 815
   #if ENABLED(S_CURVE_ACCELERATION)
816 816
     // Jerk controlled speed requires to express speed versus time, NOT steps
817 817
     uint32_t acceleration_time = ((float)(cruise_rate - initial_rate) / accel) * (STEPPER_TIMER_RATE),
818
-             deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE);
819
-
818
+             deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE),
820 819
     // And to offload calculations from the ISR, we also calculate the inverse of those times here
821
-    uint32_t acceleration_time_inverse = get_period_inverse(acceleration_time);
822
-    uint32_t deceleration_time_inverse = get_period_inverse(deceleration_time);
820
+             acceleration_time_inverse = get_period_inverse(acceleration_time),
821
+             deceleration_time_inverse = get_period_inverse(deceleration_time);
823 822
   #endif
824 823
 
825 824
   // Store new block parameters
@@ -834,6 +833,47 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
834 833
     block->cruise_rate = cruise_rate;
835 834
   #endif
836 835
   block->final_rate = final_rate;
836
+
837
+  /**
838
+   * Laser trapezoid calculations
839
+   *
840
+   * Approximate the trapezoid with the laser, incrementing the power every `entry_per` while accelerating
841
+   * and decrementing it every `exit_power_per` while decelerating, thus ensuring power is related to feedrate.
842
+   *
843
+   * LASER_POWER_INLINE_TRAPEZOID_CONT doesn't need this as it continuously approximates
844
+   *
845
+   * Note this may behave unreliably when running with S_CURVE_ACCELERATION
846
+   */
847
+  #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
848
+    if (block->laser.power > 0) { // No need to care if power == 0
849
+      const uint8_t entry_power = block->laser.power * entry_factor; // Power on block entry
850
+      #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
851
+        // Speedup power
852
+        const uint8_t entry_power_diff = block->laser.power - entry_power;
853
+        if (entry_power_diff) {
854
+          block->laser.entry_per = accelerate_steps / entry_power_diff;
855
+          block->laser.power_entry = entry_power;
856
+        }
857
+        else {
858
+          block->laser.entry_per = 0;
859
+          block->laser.power_entry = block->laser.power;
860
+        }
861
+        // Slowdown power
862
+        const uint8_t exit_power = block->laser.power * exit_factor, // Power on block entry
863
+                      exit_power_diff = block->laser.power - exit_power;
864
+        if (exit_power_diff) {
865
+          block->laser.exit_per = (block->step_event_count - block->decelerate_after) / exit_power_diff;
866
+          block->laser.power_exit = exit_power;
867
+        }
868
+        else {
869
+          block->laser.exit_per = 0;
870
+          block->laser.power_exit = block->laser.power;
871
+        }
872
+      #else
873
+        block->laser.power_entry = entry_power;
874
+      #endif
875
+    }
876
+  #endif
837 877
 }
838 878
 
839 879
 /*                            PLANNER SPEED DEFINITION
@@ -1813,6 +1853,12 @@ bool Planner::_populate_block(block_t * const block, bool split_move,
1813 1853
   // Set direction bits
1814 1854
   block->direction_bits = dm;
1815 1855
 
1856
+  // Update block laser power
1857
+  #if ENABLED(LASER_POWER_INLINE)
1858
+    block->laser.status = settings.laser.status;
1859
+    block->laser.power = settings.laser.power;
1860
+  #endif
1861
+
1816 1862
   // Number of steps for each axis
1817 1863
   // See http://www.corexy.com/theory.html
1818 1864
   #if CORE_IS_XY

+ 45
- 1
Marlin/src/module/planner.h View File

@@ -52,7 +52,7 @@
52 52
 #endif
53 53
 
54 54
 #if HAS_CUTTER
55
-  #include "../feature/spindle_laser.h"
55
+  #include "../feature/spindle_laser_types.h"
56 56
 #endif
57 57
 
58 58
 // Feedrate for manual moves
@@ -88,6 +88,23 @@ enum BlockFlag : char {
88 88
   BLOCK_FLAG_SYNC_POSITION        = _BV(BLOCK_BIT_SYNC_POSITION)
89 89
 };
90 90
 
91
+#if ENABLED(LASER_POWER_INLINE)
92
+
93
+  typedef struct {
94
+    uint8_t status,           // See planner settings for meaning
95
+            power;            // Ditto; When in trapezoid mode this is nominal power
96
+    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
97
+      uint8_t   power_entry;  // Entry power for the laser
98
+      #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
99
+        uint8_t   power_exit; // Exit power for the laser
100
+        uint32_t  entry_per,  // Steps per power increment (to avoid floats in stepper calcs)
101
+                  exit_per;   // Steps per power decrement
102
+      #endif
103
+    #endif
104
+  } block_laser_t;
105
+
106
+#endif
107
+
91 108
 /**
92 109
  * struct block_t
93 110
  *
@@ -174,12 +191,36 @@ typedef struct block_t {
174 191
     uint32_t sdpos;
175 192
   #endif
176 193
 
194
+  #if ENABLED(LASER_POWER_INLINE)
195
+    block_laser_t laser;
196
+  #endif
197
+
177 198
 } block_t;
178 199
 
179 200
 #define HAS_POSITION_FLOAT ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL)
180 201
 
181 202
 #define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
182 203
 
204
+#if ENABLED(LASER_POWER_INLINE)
205
+  typedef struct {
206
+    /**
207
+     * Laser status bitmask; most bits are unused;
208
+     *  0: Planner buffer enable
209
+     *  1: Laser enable
210
+     *  2: Reserved for direction
211
+     */
212
+    uint8_t status;
213
+    /**
214
+     * Laser power: 0 or 255 in case of PWM-less laser,
215
+     * or the OCR value;
216
+     *
217
+     * Using OCR instead of raw power,
218
+     * as it avoids floating points during move loop
219
+     */
220
+    uint8_t power;
221
+  } settings_laser_t;
222
+#endif
223
+
183 224
 typedef struct {
184 225
    uint32_t max_acceleration_mm_per_s2[XYZE_N], // (mm/s^2) M201 XYZE
185 226
             min_segment_time_us;                // (µs) M205 B
@@ -190,6 +231,9 @@ typedef struct {
190 231
             travel_acceleration;                // (mm/s^2) M204 T - Travel acceleration. DEFAULT ACCELERATION for all NON printing moves.
191 232
  feedRate_t min_feedrate_mm_s,                  // (mm/s) M205 S - Minimum linear feedrate
192 233
             min_travel_feedrate_mm_s;           // (mm/s) M205 T - Minimum travel feedrate
234
+  #if ENABLED(LASER_POWER_INLINE)
235
+    settings_laser_t laser;
236
+  #endif
193 237
 } planner_settings_t;
194 238
 
195 239
 #if DISABLED(SKEW_CORRECTION)

+ 163
- 23
Marlin/src/module/stepper.cpp View File

@@ -133,6 +133,10 @@ Stepper stepper; // Singleton
133 133
   #include "../feature/powerloss.h"
134 134
 #endif
135 135
 
136
+#if HAS_CUTTER
137
+  #include "../feature/spindle_laser.h"
138
+#endif
139
+
136 140
 // public:
137 141
 
138 142
 #if HAS_EXTRA_ENDSTOPS || ENABLED(Z_STEPPER_AUTO_ALIGN)
@@ -236,6 +240,20 @@ xyz_long_t Stepper::endstops_trigsteps;
236 240
 xyze_long_t Stepper::count_position{0};
237 241
 xyze_int8_t Stepper::count_direction{0};
238 242
 
243
+#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
244
+  Stepper::stepper_laser_t Stepper::laser = {
245
+    .trap_en = false,
246
+    .cur_power = 0,
247
+    .cruise_set = false,
248
+    #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
249
+      .last_step_count = 0,
250
+      .acc_step_count = 0
251
+    #else
252
+      .till_update = 0
253
+    #endif
254
+  };
255
+#endif
256
+
239 257
 #define DUAL_ENDSTOP_APPLY_STEP(A,V)                                                                                        \
240 258
   if (separate_multi_axis) {                                                                                                \
241 259
     if (A##_HOME_DIR < 0) {                                                                                                 \
@@ -1674,10 +1692,9 @@ uint32_t Stepper::block_phase_isr() {
1674 1692
 
1675 1693
         #if ENABLED(S_CURVE_ACCELERATION)
1676 1694
           // Get the next speed to use (Jerk limited!)
1677
-          uint32_t acc_step_rate =
1678
-            acceleration_time < current_block->acceleration_time
1679
-              ? _eval_bezier_curve(acceleration_time)
1680
-              : current_block->cruise_rate;
1695
+          uint32_t acc_step_rate = acceleration_time < current_block->acceleration_time
1696
+                                   ? _eval_bezier_curve(acceleration_time)
1697
+                                   : current_block->cruise_rate;
1681 1698
         #else
1682 1699
           acc_step_rate = STEP_MULTIPLY(acceleration_time, current_block->acceleration_rate) + current_block->initial_rate;
1683 1700
           NOMORE(acc_step_rate, current_block->nominal_rate);
@@ -1690,9 +1707,40 @@ uint32_t Stepper::block_phase_isr() {
1690 1707
         acceleration_time += interval;
1691 1708
 
1692 1709
         #if ENABLED(LIN_ADVANCE)
1693
-          // Fire ISR if final adv_rate is reached
1694
-          if (LA_steps && (!LA_use_advance_lead || LA_isr_rate != current_block->advance_speed))
1695
-            initiateLA();
1710
+          if (LA_use_advance_lead) {
1711
+            // Fire ISR if final adv_rate is reached
1712
+            if (LA_steps && LA_isr_rate != current_block->advance_speed) nextAdvanceISR = 0;
1713
+          }
1714
+          else if (LA_steps) nextAdvanceISR = 0;
1715
+        #endif
1716
+
1717
+        // Update laser - Accelerating
1718
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
1719
+          if (laser.trap_en) {
1720
+            #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
1721
+              if (current_block->laser.entry_per) {
1722
+                laser.acc_step_count -= step_events_completed - laser.last_step_count;
1723
+                laser.last_step_count = step_events_completed;
1724
+
1725
+                // Should be faster than a divide, since this should trip just once
1726
+                if (laser.acc_step_count < 0) {
1727
+                  while (laser.acc_step_count < 0) {
1728
+                    laser.acc_step_count += current_block->laser.entry_per;
1729
+                    if (laser.cur_power < current_block->laser.power) laser.cur_power++;
1730
+                  }
1731
+                  cutter.set_ocr_power(laser.cur_power);
1732
+                }
1733
+              }
1734
+            #else
1735
+              if (laser.till_update)
1736
+                laser.till_update--;
1737
+              else {
1738
+                laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
1739
+                laser.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate;
1740
+                cutter.set_ocr_power(laser.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles
1741
+              }
1742
+            #endif
1743
+          }
1696 1744
         #endif
1697 1745
       }
1698 1746
       // Are we in Deceleration phase ?
@@ -1740,10 +1788,39 @@ uint32_t Stepper::block_phase_isr() {
1740 1788
               LA_isr_rate = current_block->advance_speed;
1741 1789
             }
1742 1790
           }
1743
-          else if (LA_steps) initiateLA();
1791
+          else if (LA_steps) nextAdvanceISR = 0;
1792
+        #endif // LIN_ADVANCE
1793
+
1794
+        // Update laser - Decelerating
1795
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
1796
+          if (laser.trap_en) {
1797
+            #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
1798
+              if (current_block->laser.exit_per) {
1799
+                laser.acc_step_count -= step_events_completed - laser.last_step_count;
1800
+                laser.last_step_count = step_events_completed;
1801
+
1802
+                // Should be faster than a divide, since this should trip just once
1803
+                if (laser.acc_step_count < 0) {
1804
+                  while (laser.acc_step_count < 0) {
1805
+                    laser.acc_step_count += current_block->laser.exit_per;
1806
+                    if (laser.cur_power > current_block->laser.power_exit) laser.cur_power--;
1807
+                  }
1808
+                  cutter.set_ocr_power(laser.cur_power);
1809
+                }
1810
+              }
1811
+            #else
1812
+              if (laser.till_update)
1813
+                laser.till_update--;
1814
+              else {
1815
+                laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
1816
+                laser.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate;
1817
+                cutter.set_ocr_power(laser.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles
1818
+              }
1819
+            #endif
1820
+          }
1744 1821
         #endif
1745 1822
       }
1746
-      // We must be in cruise phase otherwise
1823
+      // Must be in cruise phase otherwise
1747 1824
       else {
1748 1825
 
1749 1826
         #if ENABLED(LIN_ADVANCE)
@@ -1759,6 +1836,22 @@ uint32_t Stepper::block_phase_isr() {
1759 1836
 
1760 1837
         // The timer interval is just the nominal value for the nominal speed
1761 1838
         interval = ticks_nominal;
1839
+
1840
+        // Update laser - Cruising
1841
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
1842
+          if (laser.trap_en) {
1843
+            if (!laser.cruise_set) {
1844
+              laser.cur_power = current_block->laser.power;
1845
+              cutter.set_ocr_power(laser.cur_power);
1846
+              laser.cruise_set = true;
1847
+            }
1848
+            #if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
1849
+              laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
1850
+            #else
1851
+              laser.last_step_count = step_events_completed;
1852
+            #endif
1853
+          }
1854
+        #endif
1762 1855
       }
1763 1856
     }
1764 1857
   }
@@ -1805,11 +1898,11 @@ uint32_t Stepper::block_phase_isr() {
1805 1898
          * If DeltaA ==  DeltaB, the movement is only in the 1st axis (X)
1806 1899
          */
1807 1900
         #if EITHER(COREXY, COREXZ)
1808
-          #define X_CMP ==
1901
+          #define X_CMP(A,B) ((A)==(B))
1809 1902
         #else
1810
-          #define X_CMP !=
1903
+          #define X_CMP(A,B) ((A)!=(B))
1811 1904
         #endif
1812
-        #define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) X_CMP D_(2)) )
1905
+        #define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && X_CMP(D_(1),D_(2))) )
1813 1906
       #else
1814 1907
         #define X_MOVE_TEST !!current_block->steps.a
1815 1908
       #endif
@@ -1823,11 +1916,11 @@ uint32_t Stepper::block_phase_isr() {
1823 1916
          * If DeltaA == -DeltaB, the movement is only in the 2nd axis (Y or Z)
1824 1917
          */
1825 1918
         #if EITHER(COREYX, COREYZ)
1826
-          #define Y_CMP ==
1919
+          #define Y_CMP(A,B) ((A)==(B))
1827 1920
         #else
1828
-          #define Y_CMP !=
1921
+          #define Y_CMP(A,B) ((A)!=(B))
1829 1922
         #endif
1830
-        #define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Y_CMP D_(2)) )
1923
+        #define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Y_CMP(D_(1),D_(2))) )
1831 1924
       #else
1832 1925
         #define Y_MOVE_TEST !!current_block->steps.b
1833 1926
       #endif
@@ -1841,11 +1934,11 @@ uint32_t Stepper::block_phase_isr() {
1841 1934
          * If DeltaA == -DeltaB, the movement is only in the 2nd axis (Z)
1842 1935
          */
1843 1936
         #if EITHER(COREZX, COREZY)
1844
-          #define Z_CMP ==
1937
+          #define Z_CMP(A,B) ((A)==(B))
1845 1938
         #else
1846
-          #define Z_CMP !=
1939
+          #define Z_CMP(A,B) ((A)!=(B))
1847 1940
         #endif
1848
-        #define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Z_CMP D_(2)) )
1941
+        #define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Z_CMP(D_(1),D_(2))) )
1849 1942
       #else
1850 1943
         #define Z_MOVE_TEST !!current_block->steps.c
1851 1944
       #endif
@@ -1938,6 +2031,39 @@ uint32_t Stepper::block_phase_isr() {
1938 2031
         set_directions();
1939 2032
       }
1940 2033
 
2034
+      #if ENABLED(LASER_POWER_INLINE)
2035
+        const uint8_t stat = current_block->laser.status;
2036
+        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
2037
+          laser.trap_en = (stat & 0x03) == 0x03;
2038
+          laser.cur_power = current_block->laser.power_entry; // RESET STATE
2039
+          laser.cruise_set = false;
2040
+          #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
2041
+            laser.last_step_count = 0;
2042
+            laser.acc_step_count = current_block->laser.entry_per / 2;
2043
+          #else
2044
+            laser.till_update = 0;
2045
+          #endif
2046
+          // Always have PWM in this case
2047
+          if (TEST(stat, 0)) {                        // Planner controls the laser
2048
+            if (TEST(stat, 1))                        // Laser is on
2049
+              cutter.set_ocr_power(laser.cur_power);
2050
+            else
2051
+              cutter.set_power(0);
2052
+          }
2053
+        #else
2054
+          if (TEST(stat, 0)) {                        // Planner controls the laser
2055
+            #if ENABLED(SPINDLE_LASER_PWM)
2056
+              if (TEST(stat, 1))                      // Laser is on
2057
+                cutter.set_ocr_power(current_block->laser.power);
2058
+              else
2059
+                cutter.set_power(0);
2060
+            #else
2061
+              cutter.set_enabled(TEST(stat, 1));
2062
+            #endif
2063
+          }
2064
+        #endif
2065
+      #endif // LASER_POWER_INLINE
2066
+
1941 2067
       // At this point, we must ensure the movement about to execute isn't
1942 2068
       // trying to force the head against a limit switch. If using interrupt-
1943 2069
       // driven change detection, and already against a limit then no call to
@@ -1957,21 +2083,35 @@ uint32_t Stepper::block_phase_isr() {
1957 2083
       // Mark the time_nominal as not calculated yet
1958 2084
       ticks_nominal = -1;
1959 2085
 
1960
-      #if DISABLED(S_CURVE_ACCELERATION)
1961
-        // Set as deceleration point the initial rate of the block
1962
-        acc_step_rate = current_block->initial_rate;
1963
-      #endif
1964
-
1965 2086
       #if ENABLED(S_CURVE_ACCELERATION)
1966 2087
         // Initialize the Bézier speed curve
1967 2088
         _calc_bezier_curve_coeffs(current_block->initial_rate, current_block->cruise_rate, current_block->acceleration_time_inverse);
1968 2089
         // We haven't started the 2nd half of the trapezoid
1969 2090
         bezier_2nd_half = false;
2091
+      #else
2092
+        // Set as deceleration point the initial rate of the block
2093
+        acc_step_rate = current_block->initial_rate;
1970 2094
       #endif
1971 2095
 
1972 2096
       // Calculate the initial timer interval
1973 2097
       interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr);
1974 2098
     }
2099
+    #if ENABLED(LASER_POWER_INLINE_CONTINUOUS)
2100
+      else { // No new block found; so apply inline laser parameters
2101
+        // This should mean ending file with 'M5 I' will stop the laser; thus the inline flag isn't needed
2102
+        const uint8_t stat = planner.settings.laser.status;
2103
+        if (TEST(stat, 0)) {             // Planner controls the laser
2104
+          #if ENABLED(SPINDLE_LASER_PWM)
2105
+            if (TEST(stat, 1))           // Laser is on
2106
+              cutter.set_ocr_power(planner.settings.laser.power);
2107
+            else
2108
+              cutter.set_power(0);
2109
+          #else
2110
+            cutter.set_enabled(TEST(stat, 1));
2111
+          #endif
2112
+        }
2113
+      }
2114
+    #endif
1975 2115
   }
1976 2116
 
1977 2117
   // Return the interval to wait

+ 20
- 8
Marlin/src/module/stepper.h View File

@@ -339,23 +339,35 @@ class Stepper {
339 339
       static uint32_t acc_step_rate; // needed for deceleration start point
340 340
     #endif
341 341
 
342
-    //
343 342
     // Exact steps at which an endstop was triggered
344
-    //
345 343
     static xyz_long_t endstops_trigsteps;
346 344
 
347
-    //
348 345
     // Positions of stepper motors, in step units
349
-    //
350 346
     static xyze_long_t count_position;
351 347
 
352
-    //
353
-    // Current direction of stepper motors (+1 or -1)
354
-    //
348
+    // Current stepper motor directions (+1 or -1)
355 349
     static xyze_int8_t count_direction;
356 350
 
357
-  public:
351
+    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
352
+
353
+      typedef struct {
354
+        bool trap_en;       // Trapezoid needed flag (i.e., laser on, planner in control)
355
+        uint8_t cur_power;  // Current laser power
356
+        bool cruise_set;    // Power set up for cruising?
357
+
358
+        #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
359
+          uint32_t last_step_count, // Step count from the last update
360
+                   acc_step_count;  // Bresenham counter for laser accel/decel
361
+        #else
362
+          uint16_t till_update;     // Countdown to the next update
363
+        #endif
364
+      } stepper_laser_t;
358 365
 
366
+      static stepper_laser_t laser;
367
+
368
+    #endif
369
+
370
+  public:
359 371
     // Initialize stepper hardware
360 372
     static void init();
361 373
 

Loading…
Cancel
Save