Преглед на файлове

⚡️ Fix and improve Inline Laser Power (#22690)

Mike La Spina преди 1 година
родител
ревизия
6a67ad4e4a

+ 33
- 71
Marlin/Configuration_adv.h Целия файл

3668
     #endif
3668
     #endif
3669
 
3669
 
3670
     // Define the minimum and maximum test pulse time values for a laser test fire function
3670
     // Define the minimum and maximum test pulse time values for a laser test fire function
3671
-    #define LASER_TEST_PULSE_MIN           1   // Used with Laser Control Menu
3672
-    #define LASER_TEST_PULSE_MAX         999   // Caution: Menu may not show more than 3 characters
3671
+    #define LASER_TEST_PULSE_MIN           1   // (ms) Used with Laser Control Menu
3672
+    #define LASER_TEST_PULSE_MAX         999   // (ms) Caution: Menu may not show more than 3 characters
3673
+
3674
+    #define SPINDLE_LASER_POWERUP_DELAY   50   // (ms) Delay to allow the spindle/laser to come up to speed/power
3675
+    #define SPINDLE_LASER_POWERDOWN_DELAY 50   // (ms) Delay to allow the spindle to stop
3673
 
3676
 
3674
    /**
3677
    /**
3675
     * Laser Safety Timeout
3678
     * Laser Safety Timeout
3682
     #define LASER_SAFETY_TIMEOUT_MS     1000   // (ms)
3685
     #define LASER_SAFETY_TIMEOUT_MS     1000   // (ms)
3683
 
3686
 
3684
     /**
3687
     /**
3685
-     * Enable inline laser power to be handled in the planner / stepper routines.
3686
-     * Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I)
3687
-     * or by the 'S' parameter in G0/G1/G2/G3 moves (see LASER_MOVE_POWER).
3688
+     * Any M3 or G1/2/3/5 command with the 'I' parameter enables continuous inline power mode.
3689
+     *
3690
+     * e.g., 'M3 I' enables continuous inline power which is processed by the planner.
3691
+     * Power is stored in move blocks and applied when blocks are processed by the Stepper ISR.
3692
+     *
3693
+     * 'M4 I' sets dynamic mode which uses the current feedrate to calculate a laser power OCR value.
3688
      *
3694
      *
3689
-     * This allows the laser to keep in perfect sync with the planner and removes
3690
-     * the powerup/down delay since lasers require negligible time.
3695
+     * Any move in dynamic mode will use the current feedrate to calculate the laser power.
3696
+     * Feed rates are set by the F parameter of a move command e.g. G1 X0 Y10 F6000
3697
+     * Laser power would be calculated by bit shifting off 8 LSB's. In binary this is div 256.
3698
+     * The calculation gives us ocr values from 0 to 255, values over F65535 will be set as 255 .
3699
+     * More refined power control such as compesation for accell/decell will be addressed in future releases.
3700
+     *
3701
+     * M5 I clears inline mode and set power to 0, M5 sets the power output to 0 but leaves inline mode on.
3691
      */
3702
      */
3692
-    //#define LASER_POWER_INLINE
3693
-
3694
-    #if ENABLED(LASER_POWER_INLINE)
3695
-      /**
3696
-       * Scale the laser's power in proportion to the movement rate.
3697
-       *
3698
-       * - Sets the entry power proportional to the entry speed over the nominal speed.
3699
-       * - Ramps the power up every N steps to approximate the speed trapezoid.
3700
-       * - Due to the limited power resolution this is only approximate.
3701
-       */
3702
-      #define LASER_POWER_INLINE_TRAPEZOID
3703
-
3704
-      /**
3705
-       * Continuously calculate the current power (nominal_power * current_rate / nominal_rate).
3706
-       * Required for accurate power with non-trapezoidal acceleration (e.g., S_CURVE_ACCELERATION).
3707
-       * This is a costly calculation so this option is discouraged on 8-bit AVR boards.
3708
-       *
3709
-       * LASER_POWER_INLINE_TRAPEZOID_CONT_PER defines how many step cycles there are between power updates. If your
3710
-       * board isn't able to generate steps fast enough (and you are using LASER_POWER_INLINE_TRAPEZOID_CONT), increase this.
3711
-       * Note that when this is zero it means it occurs every cycle; 1 means a delay wait one cycle then run, etc.
3712
-       */
3713
-      //#define LASER_POWER_INLINE_TRAPEZOID_CONT
3714
-
3715
-      /**
3716
-       * Stepper iterations between power updates. Increase this value if the board
3717
-       * can't keep up with the processing demands of LASER_POWER_INLINE_TRAPEZOID_CONT.
3718
-       * Disable (or set to 0) to recalculate power on every stepper iteration.
3719
-       */
3720
-      //#define LASER_POWER_INLINE_TRAPEZOID_CONT_PER 10
3721
-
3722
-      /**
3723
-       * Include laser power in G0/G1/G2/G3/G5 commands with the 'S' parameter
3724
-       */
3725
-      //#define LASER_MOVE_POWER
3726
-
3727
-      #if ENABLED(LASER_MOVE_POWER)
3728
-        // Turn off the laser on G0 moves with no power parameter.
3729
-        // If a power parameter is provided, use that instead.
3730
-        //#define LASER_MOVE_G0_OFF
3731
 
3703
 
3732
-        // Turn off the laser on G28 homing.
3733
-        //#define LASER_MOVE_G28_OFF
3734
-      #endif
3735
-
3736
-      /**
3737
-       * Inline flag inverted
3738
-       *
3739
-       * WARNING: M5 will NOT turn off the laser unless another move
3740
-       *          is done (so G-code files must end with 'M5 I').
3741
-       */
3742
-      //#define LASER_POWER_INLINE_INVERT
3743
-
3744
-      /**
3745
-       * Continuously apply inline power. ('M3 S3' == 'G1 S3' == 'M3 S3 I')
3746
-       *
3747
-       * The laser might do some weird things, so only enable this
3748
-       * feature if you understand the implications.
3749
-       */
3750
-      //#define LASER_POWER_INLINE_CONTINUOUS
3751
-
3752
-    #else
3753
-
3754
-      #define SPINDLE_LASER_POWERUP_DELAY     50 // (ms) Delay to allow the spindle/laser to come up to speed/power
3755
-      #define SPINDLE_LASER_POWERDOWN_DELAY   50 // (ms) Delay to allow the spindle to stop
3704
+    /**
3705
+     * Enable M3 commands for laser mode inline power planner syncing.
3706
+     * This feature enables any M3 S-value to be injected into the block buffers while in
3707
+     * CUTTER_MODE_CONTINUOUS. The option allows M3 laser power to be commited without waiting
3708
+     * for a planner syncronization
3709
+     */
3710
+    //#define LASER_POWER_SYNC
3756
 
3711
 
3757
-    #endif
3712
+    /**
3713
+     * Scale the laser's power in proportion to the movement rate.
3714
+     *
3715
+     * - Sets the entry power proportional to the entry speed over the nominal speed.
3716
+     * - Ramps the power up every N steps to approximate the speed trapezoid.
3717
+     * - Due to the limited power resolution this is only approximate.
3718
+     */
3719
+    //#define LASER_POWER_TRAP
3758
 
3720
 
3759
     //
3721
     //
3760
     // Laser I2C Ammeter (High precision INA226 low/high side module)
3722
     // Laser I2C Ammeter (High precision INA226 low/high side module)

+ 51
- 37
Marlin/src/feature/spindle_laser.cpp Целия файл

39
 #endif
39
 #endif
40
 
40
 
41
 SpindleLaser cutter;
41
 SpindleLaser cutter;
42
-uint8_t SpindleLaser::power,
42
+bool SpindleLaser::enable_state;                                      // Virtual enable state, controls enable pin if present and or apply power if > 0
43
+uint8_t SpindleLaser::power,                                          // Actual power output 0-255 ocr or "0 = off" > 0 = "on"
43
         SpindleLaser::last_power_applied; // = 0                      // Basic power state tracking
44
         SpindleLaser::last_power_applied; // = 0                      // Basic power state tracking
45
+
44
 #if ENABLED(LASER_FEATURE)
46
 #if ENABLED(LASER_FEATURE)
45
-  cutter_test_pulse_t SpindleLaser::testPulse = 50;                   // Test fire Pulse time ms value.
47
+  cutter_test_pulse_t SpindleLaser::testPulse = 50;                   // (ms) Test fire pulse default duration
48
+  uint8_t SpindleLaser::last_block_power; // = 0                      // Track power changes for dynamic inline power
49
+  feedRate_t SpindleLaser::feedrate_mm_m = 1500,
50
+             SpindleLaser::last_feedrate_mm_m; // = 0                 // (mm/min) Track feedrate changes for dynamic power
46
 #endif
51
 #endif
47
-bool SpindleLaser::isReady;                                           // Ready to apply power setting from the UI to OCR
48
-cutter_power_t SpindleLaser::menuPower,                               // Power set via LCD menu in PWM, PERCENT, or RPM
49
-               SpindleLaser::unitPower;                               // LCD status power in PWM, PERCENT, or RPM
50
 
52
 
51
-#if ENABLED(MARLIN_DEV_MODE)
52
-  cutter_frequency_t SpindleLaser::frequency;                         // PWM frequency setting; range: 2K - 50K
53
-#endif
53
+bool SpindleLaser::isReadyForUI = false;                              // Ready to apply power setting from the UI to OCR
54
+CutterMode SpindleLaser::cutter_mode = CUTTER_MODE_STANDARD;          // Default is standard mode
55
+
56
+constexpr cutter_cpower_t SpindleLaser::power_floor;
57
+cutter_power_t SpindleLaser::menuPower = 0,                           // Power value via LCD menu in PWM, PERCENT, or RPM based on configured format set by CUTTER_POWER_UNIT.
58
+               SpindleLaser::unitPower = 0;                           // Unit power is in PWM, PERCENT, or RPM based on CUTTER_POWER_UNIT.
59
+
60
+cutter_frequency_t SpindleLaser::frequency;                           // PWM frequency setting; range: 2K - 50K
61
+
54
 #define SPINDLE_LASER_PWM_OFF TERN(SPINDLE_LASER_PWM_INVERT, 255, 0)
62
 #define SPINDLE_LASER_PWM_OFF TERN(SPINDLE_LASER_PWM_INVERT, 255, 0)
55
 
63
 
56
 /**
64
 /**
65
   #if ENABLED(SPINDLE_CHANGE_DIR)
73
   #if ENABLED(SPINDLE_CHANGE_DIR)
66
     OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR);                   // Init rotation to clockwise (M3)
74
     OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR);                   // Init rotation to clockwise (M3)
67
   #endif
75
   #endif
76
+  #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
77
+    frequency = SPINDLE_LASER_FREQUENCY;
78
+    hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
79
+  #endif
68
   #if ENABLED(SPINDLE_LASER_USE_PWM)
80
   #if ENABLED(SPINDLE_LASER_USE_PWM)
69
     SET_PWM(SPINDLE_LASER_PWM_PIN);
81
     SET_PWM(SPINDLE_LASER_PWM_PIN);
70
     hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Set to lowest speed
82
     hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Set to lowest speed
71
   #endif
83
   #endif
72
-  #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
73
-    hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
74
-    TERN_(MARLIN_DEV_MODE, frequency = SPINDLE_LASER_FREQUENCY);
75
-  #endif
76
   #if ENABLED(AIR_EVACUATION)
84
   #if ENABLED(AIR_EVACUATION)
77
     OUT_WRITE(AIR_EVACUATION_PIN, !AIR_EVACUATION_ACTIVE);            // Init Vacuum/Blower OFF
85
     OUT_WRITE(AIR_EVACUATION_PIN, !AIR_EVACUATION_ACTIVE);            // Init Vacuum/Blower OFF
78
   #endif
86
   #endif
90
    */
98
    */
91
   void SpindleLaser::_set_ocr(const uint8_t ocr) {
99
   void SpindleLaser::_set_ocr(const uint8_t ocr) {
92
     #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
100
     #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
93
-      hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), TERN(MARLIN_DEV_MODE, frequency, SPINDLE_LASER_FREQUENCY));
101
+      hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency);
94
     #endif
102
     #endif
95
     hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
103
     hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
96
   }
104
   }
107
 #endif // SPINDLE_LASER_USE_PWM
115
 #endif // SPINDLE_LASER_USE_PWM
108
 
116
 
109
 /**
117
 /**
110
- * Apply power for laser/spindle
118
+ * Apply power for Laser or Spindle
111
  *
119
  *
112
  * Apply cutter power value for PWM, Servo, and on/off pin.
120
  * Apply cutter power value for PWM, Servo, and on/off pin.
113
  *
121
  *
114
- * @param opwr Power value. Range 0 to MAX. When 0 disable spindle/laser.
122
+ * @param opwr Power value. Range 0 to MAX.
115
  */
123
  */
116
 void SpindleLaser::apply_power(const uint8_t opwr) {
124
 void SpindleLaser::apply_power(const uint8_t opwr) {
117
-  if (opwr == last_power_applied) return;
118
-  last_power_applied = opwr;
119
-  power = opwr;
120
-  #if ENABLED(SPINDLE_LASER_USE_PWM)
121
-    if (cutter.unitPower == 0 && CUTTER_UNIT_IS(RPM)) {
122
-      ocr_off();
123
-      isReady = false;
124
-    }
125
-    else if (ENABLED(CUTTER_POWER_RELATIVE) || enabled()) {
126
-      set_ocr(power);
127
-      isReady = true;
128
-    }
129
-    else {
130
-      ocr_off();
131
-      isReady = false;
132
-    }
133
-  #elif ENABLED(SPINDLE_SERVO)
134
-    servo[SPINDLE_SERVO_NR].move(power);
135
-  #else
136
-    WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
137
-    isReady = true;
138
-  #endif
125
+  if (enabled() || opwr == 0) {                                   // 0 check allows us to disable where no ENA pin exists
126
+    // Test and set the last power used to improve performance
127
+    if (opwr == last_power_applied) return;
128
+    last_power_applied = opwr;
129
+    // Handle PWM driven or just simple on/off
130
+    #if ENABLED(SPINDLE_LASER_USE_PWM)
131
+      if (CUTTER_UNIT_IS(RPM) && unitPower == 0)
132
+        ocr_off();
133
+      else if (ENABLED(CUTTER_POWER_RELATIVE) || enabled() || opwr == 0) {
134
+        set_ocr(opwr);
135
+        isReadyForUI = true;
136
+      }
137
+      else
138
+        ocr_off();
139
+    #elif ENABLED(SPINDLE_SERVO)
140
+      MOVE_SERVO(SPINDLE_SERVO_NR, power);
141
+    #else
142
+      WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
143
+      isReadyForUI = true;
144
+    #endif
145
+  }
146
+  else {
147
+    #if PIN_EXISTS(SPINDLE_LASER_ENA)
148
+      WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE);
149
+    #endif
150
+    isReadyForUI = false; // Only used for UI display updates.
151
+    TERN_(SPINDLE_LASER_USE_PWM, ocr_off());
152
+  }
139
 }
153
 }
140
 
154
 
141
 #if ENABLED(SPINDLE_CHANGE_DIR)
155
 #if ENABLED(SPINDLE_CHANGE_DIR)

+ 159
- 174
Marlin/src/feature/spindle_laser.h Целия файл

34
   #include "../libs/buzzer.h"
34
   #include "../libs/buzzer.h"
35
 #endif
35
 #endif
36
 
36
 
37
-#if ENABLED(LASER_POWER_INLINE)
38
-  #include "../module/planner.h"
39
-#endif
37
+// Inline laser power
38
+#include "../module/planner.h"
40
 
39
 
41
 #define PCT_TO_PWM(X) ((X) * 255 / 100)
40
 #define PCT_TO_PWM(X) ((X) * 255 / 100)
42
 #define PCT_TO_SERVO(X) ((X) * 180 / 100)
41
 #define PCT_TO_SERVO(X) ((X) * 180 / 100)
43
 
42
 
43
+
44
+// Laser/Cutter operation mode
45
+enum CutterMode : int8_t {
46
+  CUTTER_MODE_ERROR = -1,
47
+  CUTTER_MODE_STANDARD,     // M3 power is applied directly and waits for planner moves to sync.
48
+  CUTTER_MODE_CONTINUOUS,   // M3 or G1/2/3 move power is controlled within planner blocks, set with 'M3 I', cleared with 'M5 I'.
49
+  CUTTER_MODE_DYNAMIC       // M4 laser power is proportional to the feed rate, set with 'M4 I', cleared with 'M5 I'.
50
+};
51
+
44
 class SpindleLaser {
52
 class SpindleLaser {
45
 public:
53
 public:
46
-  static const inline uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); }
54
+  static CutterMode cutter_mode;
47
 
55
 
48
-  // cpower = configured values (e.g., SPEED_POWER_MAX)
56
+  static constexpr uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); }
49
 
57
 
58
+  // cpower = configured values (e.g., SPEED_POWER_MAX)
50
   // Convert configured power range to a percentage
59
   // Convert configured power range to a percentage
51
-  static const inline uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) {
52
-    constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0),
53
-                              power_range = SPEED_POWER_MAX - power_floor;
54
-    return cpwr ? round(100.0f * (cpwr - power_floor) / power_range) : 0;
60
+  static constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0);
61
+  static constexpr uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) {
62
+    return cpwr ? round(100.0f * (cpwr - power_floor) / (SPEED_POWER_MAX - power_floor)) : 0;
55
   }
63
   }
56
 
64
 
57
-  // Convert a cpower (e.g., SPEED_POWER_STARTUP) to unit power (upwr, upower),
58
-  // which can be PWM, Percent, Servo angle, or RPM (rel/abs).
59
-  static const inline cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power
60
-    const cutter_power_t upwr = (
65
+  // Convert config defines from RPM to %, angle or PWM when in Spindle mode
66
+  // and convert from PERCENT to PWM when in Laser mode
67
+  static constexpr cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power
68
+    return (
61
       #if ENABLED(SPINDLE_FEATURE)
69
       #if ENABLED(SPINDLE_FEATURE)
62
-        // Spindle configured values are in RPM
70
+        // Spindle configured define values are in RPM
63
         #if CUTTER_UNIT_IS(RPM)
71
         #if CUTTER_UNIT_IS(RPM)
64
-          cpwr                            // to RPM
65
-        #elif CUTTER_UNIT_IS(PERCENT)     // to PCT
66
-          cpwr_to_pct(cpwr)
67
-        #elif CUTTER_UNIT_IS(SERVO)       // to SERVO angle
68
-          PCT_TO_SERVO(cpwr_to_pct(cpwr))
69
-        #else                             // to PWM
70
-          PCT_TO_PWM(cpwr_to_pct(cpwr))
72
+          cpwr                            // to same
73
+        #elif CUTTER_UNIT_IS(PERCENT)
74
+          cpwr_to_pct(cpwr)               // to Percent
75
+        #elif CUTTER_UNIT_IS(SERVO)
76
+          PCT_TO_SERVO(cpwr_to_pct(cpwr)) // to SERVO angle
77
+        #else
78
+          PCT_TO_PWM(cpwr_to_pct(cpwr))   // to PWM
71
         #endif
79
         #endif
72
       #else
80
       #else
73
-        // Laser configured values are in PCT
81
+        // Laser configured define values are in Percent
74
         #if CUTTER_UNIT_IS(PWM255)
82
         #if CUTTER_UNIT_IS(PWM255)
75
-          PCT_TO_PWM(cpwr)
83
+          PCT_TO_PWM(cpwr)                // to PWM
76
         #else
84
         #else
77
-          cpwr                            // to RPM/PCT
85
+          cpwr                            // to same
78
         #endif
86
         #endif
79
       #endif
87
       #endif
80
     );
88
     );
81
-    return upwr;
82
   }
89
   }
83
 
90
 
84
-  static const cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); }
85
-  static const cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); }
91
+  static constexpr cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); }
92
+  static constexpr cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); }
86
 
93
 
87
   #if ENABLED(LASER_FEATURE)
94
   #if ENABLED(LASER_FEATURE)
88
-    static cutter_test_pulse_t testPulse; // Test fire Pulse ms value
95
+    static cutter_test_pulse_t testPulse;                 // (ms) Test fire pulse duration
96
+    static uint8_t last_block_power;                      // Track power changes for dynamic power
97
+
98
+    static feedRate_t feedrate_mm_m, last_feedrate_mm_m;  // (mm/min) Track feedrate changes for dynamic power
99
+    static bool laser_feedrate_changed() {
100
+      const bool changed = last_feedrate_mm_m != feedrate_mm_m;
101
+      if (changed) last_feedrate_mm_m = feedrate_mm_m;
102
+      return changed;
103
+    }
89
   #endif
104
   #endif
90
 
105
 
91
-  static bool isReady;                    // Ready to apply power setting from the UI to OCR
106
+  static bool isReadyForUI;               // Ready to apply power setting from the UI to OCR
107
+  static bool enable_state;
92
   static uint8_t power,
108
   static uint8_t power,
93
                  last_power_applied;      // Basic power state tracking
109
                  last_power_applied;      // Basic power state tracking
94
 
110
 
95
-  #if ENABLED(MARLIN_DEV_MODE)
96
-    static cutter_frequency_t frequency;  // Set PWM frequency; range: 2K-50K
97
-  #endif
111
+  static cutter_frequency_t frequency;  // Set PWM frequency; range: 2K-50K
98
 
112
 
99
   static cutter_power_t menuPower,        // Power as set via LCD menu in PWM, Percentage or RPM
113
   static cutter_power_t menuPower,        // Power as set via LCD menu in PWM, Percentage or RPM
100
                         unitPower;        // Power as displayed status in PWM, Percentage or RPM
114
                         unitPower;        // Power as displayed status in PWM, Percentage or RPM
101
 
115
 
102
   static void init();
116
   static void init();
103
 
117
 
104
-  #if ENABLED(MARLIN_DEV_MODE)
118
+  #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
105
     static void refresh_frequency() { hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
119
     static void refresh_frequency() { hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
106
   #endif
120
   #endif
107
 
121
 
108
   // Modifying this function should update everywhere
122
   // Modifying this function should update everywhere
109
   static bool enabled(const cutter_power_t opwr) { return opwr > 0; }
123
   static bool enabled(const cutter_power_t opwr) { return opwr > 0; }
110
-  static bool enabled() { return enabled(power); }
124
+  static bool enabled() { return enable_state; }
111
 
125
 
112
   static void apply_power(const uint8_t inpow);
126
   static void apply_power(const uint8_t inpow);
113
 
127
 
114
   FORCE_INLINE static void refresh() { apply_power(power); }
128
   FORCE_INLINE static void refresh() { apply_power(power); }
115
-  FORCE_INLINE static void set_power(const uint8_t upwr) { power = upwr; refresh(); }
116
 
129
 
117
   #if ENABLED(SPINDLE_LASER_USE_PWM)
130
   #if ENABLED(SPINDLE_LASER_USE_PWM)
118
 
131
 
123
     public:
136
     public:
124
 
137
 
125
     static void set_ocr(const uint8_t ocr);
138
     static void set_ocr(const uint8_t ocr);
126
-    static void ocr_set_power(const uint8_t ocr) { power = ocr; set_ocr(ocr); }
127
     static void ocr_off();
139
     static void ocr_off();
128
 
140
 
129
     /**
141
     /**
141
       );
153
       );
142
     }
154
     }
143
 
155
 
144
-    /**
145
-     * Correct power to configured range
146
-     */
147
-    static cutter_power_t power_to_range(const cutter_power_t pwr) {
148
-      return power_to_range(pwr, _CUTTER_POWER(CUTTER_POWER_UNIT));
149
-    }
150
-
151
-    static cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit) {
152
-      static constexpr float
153
-        min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)),
154
-        max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX);
155
-      if (pwr <= 0) return 0;
156
-      cutter_power_t upwr;
157
-      switch (pwrUnit) {
158
-        case _CUTTER_POWER_PWM255:
159
-          upwr = cutter_power_t(
160
-              (pwr < pct_to_ocr(min_pct)) ? pct_to_ocr(min_pct) // Use minimum if set below
161
-            : (pwr > pct_to_ocr(max_pct)) ? pct_to_ocr(max_pct) // Use maximum if set above
162
-            :  pwr
163
-          );
164
-          break;
165
-        case _CUTTER_POWER_PERCENT:
166
-          upwr = cutter_power_t(
167
-              (pwr < min_pct) ? min_pct                         // Use minimum if set below
168
-            : (pwr > max_pct) ? max_pct                         // Use maximum if set above
169
-            :  pwr                                              // PCT
170
-          );
171
-          break;
172
-        case _CUTTER_POWER_RPM:
173
-          upwr = cutter_power_t(
174
-              (pwr < SPEED_POWER_MIN) ? SPEED_POWER_MIN         // Use minimum if set below
175
-            : (pwr > SPEED_POWER_MAX) ? SPEED_POWER_MAX         // Use maximum if set above
176
-            : pwr                                               // Calculate OCR value
177
-          );
178
-          break;
179
-        default: break;
180
-      }
181
-      return upwr;
182
-    }
183
-
184
   #endif // SPINDLE_LASER_USE_PWM
156
   #endif // SPINDLE_LASER_USE_PWM
185
 
157
 
186
   /**
158
   /**
187
-   * Enable/Disable spindle/laser
188
-   * @param enable true = enable; false = disable
159
+   * Correct power to configured range
189
    */
160
    */
190
-  static void set_enabled(const bool enable) {
191
-    uint8_t value = 0;
192
-    if (enable) {
193
-      #if ENABLED(SPINDLE_LASER_USE_PWM)
194
-        if (power)
195
-          value = power;
196
-        else if (unitPower)
197
-          value = upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP));
198
-      #else
199
-        value = 255;
200
-      #endif
161
+  static cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit=_CUTTER_POWER(CUTTER_POWER_UNIT)) {
162
+    static constexpr float
163
+      min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)),
164
+      max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX);
165
+    if (pwr <= 0) return 0;
166
+    cutter_power_t upwr;
167
+    switch (pwrUnit) {
168
+      case _CUTTER_POWER_PWM255: {  // PWM
169
+        const uint8_t pmin = pct_to_ocr(min_pct), pmax = pct_to_ocr(max_pct);
170
+        upwr = cutter_power_t(constrain(pwr, pmin, pmax));
171
+      } break;
172
+      case _CUTTER_POWER_PERCENT:   // Percent
173
+        upwr = cutter_power_t(constrain(pwr, min_pct, max_pct));
174
+        break;
175
+      case _CUTTER_POWER_RPM:       // Calculate OCR value
176
+        upwr = cutter_power_t(constrain(pwr, SPEED_POWER_MIN, SPEED_POWER_MAX));
177
+        break;
178
+      default: break;
201
     }
179
     }
202
-    set_power(value);
180
+    return upwr;
203
   }
181
   }
204
 
182
 
205
-  static void disable() { isReady = false; set_enabled(false); }
206
-
207
   /**
183
   /**
208
-   * Wait for spindle to spin up or spin down
184
+   * Enable Laser or Spindle output.
185
+   * It's important to prevent changing the power output value during inline cutter operation.
186
+   * Inline power is adjusted in the planner to support LASER_TRAP_POWER and CUTTER_MODE_DYNAMIC mode.
187
+   *
188
+   * This method accepts one of the following control states:
189
+   *
190
+   *  - For CUTTER_MODE_STANDARD the cutter power is either full on/off or ocr-based and it will apply
191
+   *    SPEED_POWER_STARTUP if no value is assigned.
209
    *
192
    *
210
-   * @param on true = state to on; false = state to off.
193
+   *  - For CUTTER_MODE_CONTINUOUS inline and power remains where last set and the cutter output enable flag is set.
194
+   *
195
+   *  - CUTTER_MODE_DYNAMIC is also inline-based and it just sets the enable output flag.
196
+   *
197
+   *  - For CUTTER_MODE_ERROR set the output enable_state flag directly and set power to 0 for any mode.
198
+   *    This mode allows a global power shutdown action to occur.
211
    */
199
    */
212
-  static void power_delay(const bool on) {
213
-    #if DISABLED(LASER_POWER_INLINE)
214
-      safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
200
+  static void set_enabled(const bool enable) {
201
+    switch (cutter_mode) {
202
+      case CUTTER_MODE_STANDARD:
203
+        apply_power(enable ? TERN(SPINDLE_LASER_USE_PWM, (power ?: (unitPower ? upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP)) : 0)), 255) : 0);
204
+        break;
205
+      case CUTTER_MODE_CONTINUOUS:
206
+        TERN_(LASER_FEATURE, set_inline_enabled(enable));
207
+        break;
208
+      case CUTTER_MODE_DYNAMIC:
209
+        TERN_(LASER_FEATURE, set_inline_enabled(enable));
210
+        break;
211
+      case CUTTER_MODE_ERROR: // Error mode, no enable and kill power.
212
+        enable_state = false;
213
+        apply_power(0);
214
+    }
215
+    #if SPINDLE_LASER_ENA_PIN
216
+      WRITE(SPINDLE_LASER_ENA_PIN, enable ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
215
     #endif
217
     #endif
218
+    enable_state = enable;
219
+  }
220
+
221
+  static void disable() { isReadyForUI = false; set_enabled(false); }
222
+
223
+  // Wait for spindle/laser to startup or shutdown
224
+  static void power_delay(const bool on) {
225
+    safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
216
   }
226
   }
217
 
227
 
218
   #if ENABLED(SPINDLE_CHANGE_DIR)
228
   #if ENABLED(SPINDLE_CHANGE_DIR)
224
   #endif
234
   #endif
225
 
235
 
226
   #if ENABLED(AIR_EVACUATION)
236
   #if ENABLED(AIR_EVACUATION)
227
-    static void air_evac_enable();         // Turn On Cutter Vacuum or Laser Blower motor
228
-    static void air_evac_disable();        // Turn Off Cutter Vacuum or Laser Blower motor
229
-    static void air_evac_toggle();         // Toggle Cutter Vacuum or Laser Blower motor
230
-    static bool air_evac_state() {  // Get current state
237
+    static void air_evac_enable();     // Turn On Cutter Vacuum or Laser Blower motor
238
+    static void air_evac_disable();    // Turn Off Cutter Vacuum or Laser Blower motor
239
+    static void air_evac_toggle();     // Toggle Cutter Vacuum or Laser Blower motor
240
+    static bool air_evac_state() {     // Get current state
231
       return (READ(AIR_EVACUATION_PIN) == AIR_EVACUATION_ACTIVE);
241
       return (READ(AIR_EVACUATION_PIN) == AIR_EVACUATION_ACTIVE);
232
     }
242
     }
233
   #endif
243
   #endif
234
 
244
 
235
   #if ENABLED(AIR_ASSIST)
245
   #if ENABLED(AIR_ASSIST)
236
-    static void air_assist_enable();         // Turn on air assist
237
-    static void air_assist_disable();        // Turn off air assist
238
-    static void air_assist_toggle();         // Toggle air assist
239
-    static bool air_assist_state() {  // Get current state
246
+    static void air_assist_enable();   // Turn on air assist
247
+    static void air_assist_disable();  // Turn off air assist
248
+    static void air_assist_toggle();   // Toggle air assist
249
+    static bool air_assist_state() {   // Get current state
240
       return (READ(AIR_ASSIST_PIN) == AIR_ASSIST_ACTIVE);
250
       return (READ(AIR_ASSIST_PIN) == AIR_ASSIST_ACTIVE);
241
     }
251
     }
242
   #endif
252
   #endif
243
 
253
 
244
   #if HAS_MARLINUI_MENU
254
   #if HAS_MARLINUI_MENU
245
-    static void enable_with_dir(const bool reverse) {
246
-      isReady = true;
247
-      const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255);
248
-      if (menuPower)
249
-        power = ocr;
250
-      else
251
-        menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
252
-      unitPower = menuPower;
253
-      set_reverse(reverse);
254
-      set_enabled(true);
255
-    }
256
-    FORCE_INLINE static void enable_forward() { enable_with_dir(false); }
257
-    FORCE_INLINE static void enable_reverse() { enable_with_dir(true); }
258
-    FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); }
255
+
256
+    #if ENABLED(SPINDLE_FEATURE)
257
+      static void enable_with_dir(const bool reverse) {
258
+        isReadyForUI = true;
259
+        const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255);
260
+        if (menuPower)
261
+          power = ocr;
262
+        else
263
+          menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
264
+        unitPower = menuPower;
265
+        set_reverse(reverse);
266
+        set_enabled(true);
267
+      }
268
+      FORCE_INLINE static void enable_forward() { enable_with_dir(false); }
269
+      FORCE_INLINE static void enable_reverse() { enable_with_dir(true); }
270
+      FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); }
271
+    #endif // SPINDLE_FEATURE
259
 
272
 
260
     #if ENABLED(SPINDLE_LASER_USE_PWM)
273
     #if ENABLED(SPINDLE_LASER_USE_PWM)
261
       static void update_from_mpower() {
274
       static void update_from_mpower() {
262
-        if (isReady) power = upower_to_ocr(menuPower);
275
+        if (isReadyForUI) power = upower_to_ocr(menuPower);
263
         unitPower = menuPower;
276
         unitPower = menuPower;
264
       }
277
       }
265
     #endif
278
     #endif
266
 
279
 
267
     #if ENABLED(LASER_FEATURE)
280
     #if ENABLED(LASER_FEATURE)
281
+      // Toggle the laser on/off with menuPower. Apply SPEED_POWER_STARTUP if it was 0 on entry.
282
+      static void laser_menu_toggle(const bool state) {
283
+        set_enabled(state);
284
+        if (state) {
285
+          if (!menuPower) menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
286
+          power = upower_to_ocr(menuPower);
287
+          apply_power(power);
288
+        }
289
+      }
290
+
268
       /**
291
       /**
269
        * Test fire the laser using the testPulse ms duration
292
        * Test fire the laser using the testPulse ms duration
270
        * Also fires with any PWM power that was previous set
293
        * Also fires with any PWM power that was previous set
272
        */
295
        */
273
       static void test_fire_pulse() {
296
       static void test_fire_pulse() {
274
         TERN_(HAS_BEEPER, buzzer.tone(30, 3000));
297
         TERN_(HAS_BEEPER, buzzer.tone(30, 3000));
275
-        enable_forward();                  // Turn Laser on (Spindle speak but same funct)
298
+        cutter_mode = CUTTER_MODE_STANDARD;// Menu needs standard mode.
299
+        laser_menu_toggle(true);           // Laser On
276
         delay(testPulse);                  // Delay for time set by user in pulse ms menu screen.
300
         delay(testPulse);                  // Delay for time set by user in pulse ms menu screen.
277
-        disable();                         // Turn laser off
301
+        laser_menu_toggle(false);          // Laser Off
278
       }
302
       }
279
-    #endif
303
+    #endif // LASER_FEATURE
280
 
304
 
281
   #endif // HAS_MARLINUI_MENU
305
   #endif // HAS_MARLINUI_MENU
282
 
306
 
283
-  #if ENABLED(LASER_POWER_INLINE)
284
-    /**
285
-     * Inline power adds extra fields to the planner block
286
-     * to handle laser power and scale to movement speed.
287
-     */
307
+  #if ENABLED(LASER_FEATURE)
288
 
308
 
289
-    // Force disengage planner power control
290
-    static void inline_disable() {
291
-      isReady = false;
292
-      unitPower = 0;
293
-      planner.laser_inline.status.isPlanned = false;
294
-      planner.laser_inline.status.isEnabled = false;
295
-      planner.laser_inline.power = 0;
309
+    // Dynamic mode rate calculation
310
+    static uint8_t calc_dynamic_power() {
311
+      if (feedrate_mm_m > 65535) return 255;         // Too fast, go always on
312
+      uint16_t rate = uint16_t(feedrate_mm_m);       // 16 bits from the G-code parser float input
313
+      rate >>= 8;                                    // Take the G-code input e.g. F40000 and shift off the lower bits to get an OCR value from 1-255
314
+      return uint8_t(rate);
296
     }
315
     }
297
 
316
 
298
     // Inline modes of all other functions; all enable planner inline power control
317
     // Inline modes of all other functions; all enable planner inline power control
299
-    static void set_inline_enabled(const bool enable) {
300
-      if (enable)
301
-        inline_power(255);
302
-      else {
303
-        isReady = false;
304
-        unitPower = menuPower = 0;
305
-        planner.laser_inline.status.isPlanned = false;
306
-        TERN(SPINDLE_LASER_USE_PWM, inline_ocr_power, inline_power)(0);
307
-      }
308
-    }
318
+    static void set_inline_enabled(const bool enable) { planner.laser_inline.status.isEnabled = enable;}
309
 
319
 
310
     // Set the power for subsequent movement blocks
320
     // Set the power for subsequent movement blocks
311
-    static void inline_power(const cutter_power_t upwr) {
312
-      unitPower = menuPower = upwr;
313
-      #if ENABLED(SPINDLE_LASER_USE_PWM)
314
-        #if ENABLED(SPEED_POWER_RELATIVE) && !CUTTER_UNIT_IS(RPM) // relative mode does not turn laser off at 0, except for RPM
315
-          planner.laser_inline.status.isEnabled = true;
316
-          planner.laser_inline.power = upower_to_ocr(upwr);
317
-          isReady = true;
318
-        #else
319
-          inline_ocr_power(upower_to_ocr(upwr));
320
-        #endif
321
-      #else
322
-        planner.laser_inline.status.isEnabled = enabled(upwr);
323
-        planner.laser_inline.power = upwr;
324
-        isReady = enabled(upwr);
325
-      #endif
321
+    static void inline_power(const cutter_power_t cpwr) {
322
+      TERN(SPINDLE_LASER_USE_PWM, power = planner.laser_inline.power = cpwr, planner.laser_inline.power = cpwr > 0 ? 255 : 0);
326
     }
323
     }
327
 
324
 
328
-    static void inline_direction(const bool) { /* never */ }
329
-
330
-    #if ENABLED(SPINDLE_LASER_USE_PWM)
331
-      static void inline_ocr_power(const uint8_t ocrpwr) {
332
-        isReady = ocrpwr > 0;
333
-        planner.laser_inline.status.isEnabled = ocrpwr > 0;
334
-        planner.laser_inline.power = ocrpwr;
335
-      }
336
-    #endif
337
-  #endif // LASER_POWER_INLINE
325
+  #endif // LASER_FEATURE
338
 
326
 
339
-  static void kill() {
340
-    TERN_(LASER_POWER_INLINE, inline_disable());
341
-    disable();
342
-  }
327
+  static void kill() { disable(); }
343
 };
328
 };
344
 
329
 
345
 extern SpindleLaser cutter;
330
 extern SpindleLaser cutter;

+ 2
- 4
Marlin/src/feature/spindle_laser_types.h Целия файл

74
   #endif
74
   #endif
75
 #endif
75
 #endif
76
 
76
 
77
+typedef uint16_t cutter_frequency_t;
78
+
77
 #if ENABLED(LASER_FEATURE)
79
 #if ENABLED(LASER_FEATURE)
78
   typedef uint16_t cutter_test_pulse_t;
80
   typedef uint16_t cutter_test_pulse_t;
79
   #define CUTTER_MENU_PULSE_TYPE uint16_3
81
   #define CUTTER_MENU_PULSE_TYPE uint16_3
80
-#endif
81
-
82
-#if ENABLED(MARLIN_DEV_MODE)
83
-  typedef uint16_t cutter_frequency_t;
84
   #define CUTTER_MENU_FREQUENCY_TYPE uint16_5
82
   #define CUTTER_MENU_FREQUENCY_TYPE uint16_5
85
 #endif
83
 #endif

+ 7
- 2
Marlin/src/gcode/calibrate/G28.cpp Целия файл

59
   #include "../../libs/L64XX/L64XX_Marlin.h"
59
   #include "../../libs/L64XX/L64XX_Marlin.h"
60
 #endif
60
 #endif
61
 
61
 
62
-#if ENABLED(LASER_MOVE_G28_OFF)
62
+#if ENABLED(LASER_FEATURE)
63
   #include "../../feature/spindle_laser.h"
63
   #include "../../feature/spindle_laser.h"
64
 #endif
64
 #endif
65
 
65
 
205
   DEBUG_SECTION(log_G28, "G28", DEBUGGING(LEVELING));
205
   DEBUG_SECTION(log_G28, "G28", DEBUGGING(LEVELING));
206
   if (DEBUGGING(LEVELING)) log_machine_info();
206
   if (DEBUGGING(LEVELING)) log_machine_info();
207
 
207
 
208
-  TERN_(LASER_MOVE_G28_OFF, cutter.set_inline_enabled(false));  // turn off laser
208
+  /*
209
+   * Set the laser power to false to stop the planner from processing the current power setting.
210
+   */
211
+  #if ENABLED(LASER_FEATURE)
212
+    planner.laser_inline.status.isPowered = false;
213
+  #endif
209
 
214
 
210
   #if ENABLED(DUAL_X_CARRIAGE)
215
   #if ENABLED(DUAL_X_CARRIAGE)
211
     bool IDEX_saved_duplication_state = extruder_duplication_enabled;
216
     bool IDEX_saved_duplication_state = extruder_duplication_enabled;

+ 72
- 62
Marlin/src/gcode/control/M3-M5.cpp Целия файл

31
 /**
31
 /**
32
  * Laser:
32
  * Laser:
33
  *  M3 - Laser ON/Power (Ramped power)
33
  *  M3 - Laser ON/Power (Ramped power)
34
- *  M4 - Laser ON/Power (Continuous power)
34
+ *  M4 - Laser ON/Power (Ramped power)
35
+ *  M5 - Set power output to 0 (leaving inline mode unchanged).
36
+ *
37
+ *  M3I - Enable continuous inline power to be processed by the planner, with power
38
+ *        calculated and set in the planner blocks, processed inline during stepping.
39
+ *        Within inline mode M3 S-Values will set the power for the next moves e.g. G1 X10 Y10 powers on with the last S-Value.
40
+ *        M3I must be set before using planner-synced M3 inline S-Values (LASER_POWER_SYNC).
41
+ *
42
+ *  M4I - Set dynamic mode which calculates laser power OCR based on the current feedrate.
43
+ *
44
+ *  M5I - Clear inline mode and set power to 0.
35
  *
45
  *
36
  * Spindle:
46
  * Spindle:
37
  *  M3 - Spindle ON (Clockwise)
47
  *  M3 - Spindle ON (Clockwise)
38
  *  M4 - Spindle ON (Counter-clockwise)
48
  *  M4 - Spindle ON (Counter-clockwise)
49
+ *  M5 - Spindle OFF
39
  *
50
  *
40
  * Parameters:
51
  * Parameters:
41
- *  S<power> - Set power. S0 will turn the spindle/laser off, except in relative mode.
42
- *  O<ocr>   - Set power and OCR (oscillator count register)
52
+ *  S<power> - Set power. S0 will turn the spindle/laser off.
43
  *
53
  *
44
- *  If no PWM pin is defined then M3/M4 just turns it on.
54
+ *  If no PWM pin is defined then M3/M4 just turns it on or off.
45
  *
55
  *
46
  *  At least 12.8kHz (50Hz * 256) is needed for Spindle PWM.
56
  *  At least 12.8kHz (50Hz * 256) is needed for Spindle PWM.
47
  *  Hardware PWM is required on AVR. ISRs are too slow.
57
  *  Hardware PWM is required on AVR. ISRs are too slow.
70
     reset_stepper_timeout(); // Reset timeout to allow subsequent G-code to power the laser (imm.)
80
     reset_stepper_timeout(); // Reset timeout to allow subsequent G-code to power the laser (imm.)
71
   #endif
81
   #endif
72
 
82
 
73
-  #if EITHER(SPINDLE_LASER_USE_PWM, SPINDLE_SERVO)
74
-    auto get_s_power = [] {
75
-      if (parser.seenval('S')) {
76
-        const float spwr = parser.value_float();
77
-        #if ENABLED(SPINDLE_SERVO)
78
-          cutter.unitPower = spwr;
79
-        #else
80
-          cutter.unitPower = TERN(SPINDLE_LASER_USE_PWM,
81
-                                cutter.power_to_range(cutter_power_t(round(spwr))),
82
-                                spwr > 0 ? 255 : 0);
83
-        #endif
84
-      }
85
-      else
86
-        cutter.unitPower = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP);
87
-      return cutter.unitPower;
88
-    };
89
-  #endif
83
+  if (cutter.cutter_mode == CUTTER_MODE_STANDARD)
84
+    planner.synchronize();   // Wait for previous movement commands (G0/G1/G2/G3) to complete before changing power
90
 
85
 
91
-  #if ENABLED(LASER_POWER_INLINE)
92
-    if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
93
-      // Laser power in inline mode
94
-      cutter.inline_direction(is_M4); // Should always be unused
95
-      #if ENABLED(SPINDLE_LASER_USE_PWM)
96
-        if (parser.seenval('O')) {
97
-          cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0);
98
-          cutter.inline_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t)
99
-        }
100
-        else
101
-          cutter.inline_power(cutter.upower_to_ocr(get_s_power()));
102
-      #else
103
-        cutter.set_inline_enabled(true);
104
-      #endif
105
-      return;
86
+  #if ENABLED(LASER_FEATURE)
87
+    if (parser.seen_test('I')) {
88
+      cutter.cutter_mode = is_M4 ? CUTTER_MODE_DYNAMIC : CUTTER_MODE_CONTINUOUS;
89
+      cutter.inline_power(0);
90
+      cutter.set_enabled(true);
106
     }
91
     }
107
-    // Non-inline, standard case
108
-    cutter.inline_disable(); // Prevent future blocks re-setting the power
109
   #endif
92
   #endif
110
 
93
 
111
-  planner.synchronize();   // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power
112
-  cutter.set_reverse(is_M4);
113
-
114
-  #if ENABLED(SPINDLE_LASER_USE_PWM)
115
-    if (parser.seenval('O')) {
116
-      cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0);
117
-      cutter.ocr_set_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t)
94
+  auto get_s_power = [] {
95
+    float u;
96
+    if (parser.seenval('S')) {
97
+      const float v = parser.value_float();
98
+      u = TERN(LASER_POWER_TRAP, v, cutter.power_to_range(v));
118
     }
99
     }
119
-    else
120
-      cutter.set_power(cutter.upower_to_ocr(get_s_power()));
121
-  #elif ENABLED(SPINDLE_SERVO)
122
-    cutter.set_power(get_s_power());
123
-  #else
100
+    else if (cutter.cutter_mode == CUTTER_MODE_STANDARD)
101
+      u = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP);
102
+
103
+    cutter.menuPower = cutter.unitPower = u;
104
+
105
+    // PWM not implied, power converted to OCR from unit definition and on/off if not PWM.
106
+    cutter.power = TERN(SPINDLE_LASER_USE_PWM, cutter.upower_to_ocr(u), u > 0 ? 255 : 0);
107
+    return u;
108
+  };
109
+
110
+  if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS || cutter.cutter_mode == CUTTER_MODE_DYNAMIC) {  // Laser power in inline mode
111
+    #if ENABLED(LASER_FEATURE)
112
+      planner.laser_inline.status.isPowered = true;                                                 // M3 or M4 is powered either way
113
+      get_s_power();                                                                                // Update cutter.power if seen
114
+      #if ENABLED(LASER_POWER_SYNC)
115
+        // With power sync we only set power so it does not effect queued inline power sets
116
+        planner.buffer_sync_block(BLOCK_BIT_LASER_PWR);                                            // Send the flag, queueing inline power
117
+      #else
118
+        planner.synchronize();
119
+        cutter.inline_power(cutter.power);
120
+      #endif
121
+    #endif
122
+  }
123
+  else {
124
     cutter.set_enabled(true);
124
     cutter.set_enabled(true);
125
-  #endif
126
-  cutter.menuPower = cutter.unitPower;
125
+    get_s_power();
126
+    cutter.apply_power(
127
+      #if ENABLED(SPINDLE_SERVO)
128
+        cutter.unitPower
129
+      #elif ENABLED(SPINDLE_LASER_USE_PWM)
130
+        cutter.upower_to_ocr(cutter.unitPower)
131
+      #else
132
+        cutter.unitPower > 0 ? 255 : 0
133
+      #endif
134
+    );
135
+    TERN_(SPINDLE_CHANGE_DIR, cutter.set_reverse(is_M4));
136
+  }
127
 }
137
 }
128
 
138
 
129
 /**
139
 /**
130
  * M5 - Cutter OFF (when moves are complete)
140
  * M5 - Cutter OFF (when moves are complete)
131
  */
141
  */
132
 void GcodeSuite::M5() {
142
 void GcodeSuite::M5() {
133
-  #if ENABLED(LASER_POWER_INLINE)
134
-    if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
135
-      cutter.set_inline_enabled(false); // Laser power in inline mode
136
-      return;
137
-    }
138
-    // Non-inline, standard case
139
-    cutter.inline_disable(); // Prevent future blocks re-setting the power
140
-  #endif
141
   planner.synchronize();
143
   planner.synchronize();
142
-  cutter.set_enabled(false);
143
-  cutter.menuPower = cutter.unitPower;
144
+  cutter.power = 0;
145
+  cutter.apply_power(0);                          // M5 just kills power, leaving inline mode unchanged
146
+  if (cutter.cutter_mode != CUTTER_MODE_STANDARD) {
147
+    if (parser.seen_test('I')) {
148
+      TERN_(LASER_FEATURE, cutter.inline_power(cutter.power));
149
+      cutter.set_enabled(false);                  // Needs to happen while we are in inline mode to clear inline power.
150
+      cutter.cutter_mode = CUTTER_MODE_STANDARD;  // Switch from inline to standard mode.
151
+    }
152
+  }
153
+  cutter.set_enabled(false);                      // Disable enable output setting
144
 }
154
 }
145
 
155
 
146
 #endif // HAS_CUTTER
156
 #endif // HAS_CUTTER

+ 27
- 10
Marlin/src/gcode/gcode.cpp Целия файл

53
   #include "../feature/cancel_object.h"
53
   #include "../feature/cancel_object.h"
54
 #endif
54
 #endif
55
 
55
 
56
-#if ENABLED(LASER_MOVE_POWER)
56
+#if ENABLED(LASER_FEATURE)
57
   #include "../feature/spindle_laser.h"
57
   #include "../feature/spindle_laser.h"
58
 #endif
58
 #endif
59
 
59
 
210
       recovery.save();
210
       recovery.save();
211
   #endif
211
   #endif
212
 
212
 
213
-  if (parser.floatval('F') > 0)
213
+  if (parser.floatval('F') > 0) {
214
     feedrate_mm_s = parser.value_feedrate();
214
     feedrate_mm_s = parser.value_feedrate();
215
+    // Update the cutter feed rate for use by M4 I set inline moves.
216
+    TERN_(LASER_FEATURE, cutter.feedrate_mm_m = MMS_TO_MMM(feedrate_mm_s));
217
+  }
215
 
218
 
216
   #if BOTH(PRINTCOUNTER, HAS_EXTRUDERS)
219
   #if BOTH(PRINTCOUNTER, HAS_EXTRUDERS)
217
     if (!DEBUGGING(DRYRUN) && !skip_move)
220
     if (!DEBUGGING(DRYRUN) && !skip_move)
223
     M165();
226
     M165();
224
   #endif
227
   #endif
225
 
228
 
226
-  #if ENABLED(LASER_MOVE_POWER)
227
-    // Set the laser power in the planner to configure this move
228
-    if (parser.seen('S')) {
229
-      const float spwr = parser.value_float();
230
-      cutter.inline_power(TERN(SPINDLE_LASER_USE_PWM, cutter.power_to_range(cutter_power_t(round(spwr))), spwr > 0 ? 255 : 0));
229
+  #if ENABLED(LASER_FEATURE)
230
+    if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS || cutter.cutter_mode == CUTTER_MODE_DYNAMIC) {
231
+      // Set the cutter power in the planner to configure this move
232
+      cutter.last_feedrate_mm_m = 0;
233
+      if (WITHIN(parser.codenum, 1, TERN(ARC_SUPPORT, 3, 1)) || TERN0(BEZIER_CURVE_SUPPORT, parser.codenum == 5)) {
234
+        planner.laser_inline.status.isPowered = true;
235
+        if (parser.seen('I')) cutter.set_enabled(true);       // This is set for backward LightBurn compatibility.
236
+        if (parser.seen('S')) {
237
+          const float v = parser.value_float(),
238
+                      u = TERN(LASER_POWER_TRAP, v, cutter.power_to_range(v));
239
+          cutter.menuPower = cutter.unitPower = u;
240
+          cutter.inline_power(TERN(SPINDLE_LASER_USE_PWM, cutter.upower_to_ocr(u), u > 0 ? 255 : 0));
241
+        }
242
+      }
243
+      else if (parser.codenum == 0) {
244
+        // For dynamic mode we need to flag isPowered off, dynamic power is calculated in the stepper based on feedrate.
245
+        if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC) planner.laser_inline.status.isPowered = false;
246
+        cutter.inline_power(0); // This is planner-based so only set power and do not disable inline control flags.
247
+      }
231
     }
248
     }
232
-    else if (ENABLED(LASER_MOVE_G0_OFF) && parser.codenum == 0) // G0
233
-      cutter.set_inline_enabled(false);
234
-  #endif
249
+    else if (parser.codenum == 0)
250
+      cutter.apply_power(0);
251
+  #endif // LASER_FEATURE
235
 }
252
 }
236
 
253
 
237
 /**
254
 /**

+ 23
- 22
Marlin/src/inc/SanityCheck.h Целия файл

447
   #error "SPINDLE_LASER_ACTIVE_HIGH is now SPINDLE_LASER_ACTIVE_STATE."
447
   #error "SPINDLE_LASER_ACTIVE_HIGH is now SPINDLE_LASER_ACTIVE_STATE."
448
 #elif defined(SPINDLE_LASER_ENABLE_INVERT)
448
 #elif defined(SPINDLE_LASER_ENABLE_INVERT)
449
   #error "SPINDLE_LASER_ENABLE_INVERT is now SPINDLE_LASER_ACTIVE_STATE."
449
   #error "SPINDLE_LASER_ENABLE_INVERT is now SPINDLE_LASER_ACTIVE_STATE."
450
+#elif defined(LASER_POWER_INLINE)
451
+  #error "LASER_POWER_INLINE is not required, inline mode is enabled with 'M3 I' and disabled with 'M5 I'."
452
+#elif defined(LASER_POWER_INLINE_TRAPEZOID)
453
+  #error "LASER_POWER_INLINE_TRAPEZOID is now LASER_POWER_TRAP."
454
+#elif defined(LASER_POWER_INLINE_TRAPEZOID_CONT)
455
+  #error "LASER_POWER_INLINE_TRAPEZOID_CONT is replaced with LASER_POWER_TRAP."
456
+#elif defined(LASER_POWER_INLINE_TRAPEZOID_PER)
457
+  #error "LASER_POWER_INLINE_TRAPEZOID_CONT_PER  replaced with LASER_POWER_TRAP."
458
+#elif defined(LASER_POWER_INLINE_CONTINUOUS)
459
+  #error "LASER_POWER_INLINE_CONTINUOUS is not required, inline mode is enabled with 'M3 I' and disabled with 'M5 I'."
450
 #elif defined(CUTTER_POWER_DISPLAY)
460
 #elif defined(CUTTER_POWER_DISPLAY)
451
   #error "CUTTER_POWER_DISPLAY is now CUTTER_POWER_UNIT."
461
   #error "CUTTER_POWER_DISPLAY is now CUTTER_POWER_UNIT."
452
 #elif defined(CHAMBER_HEATER_PIN)
462
 #elif defined(CHAMBER_HEATER_PIN)
595
   #error "ARC_SUPPORT no longer uses ARC_SEGMENTS_PER_R."
605
   #error "ARC_SUPPORT no longer uses ARC_SEGMENTS_PER_R."
596
 #elif ENABLED(ARC_SUPPORT) && (!defined(MIN_ARC_SEGMENT_MM) || !defined(MAX_ARC_SEGMENT_MM))
606
 #elif ENABLED(ARC_SUPPORT) && (!defined(MIN_ARC_SEGMENT_MM) || !defined(MAX_ARC_SEGMENT_MM))
597
   #error "ARC_SUPPORT now requires MIN_ARC_SEGMENT_MM and MAX_ARC_SEGMENT_MM."
607
   #error "ARC_SUPPORT now requires MIN_ARC_SEGMENT_MM and MAX_ARC_SEGMENT_MM."
608
+#elif defined(LASER_POWER_INLINE)
609
+  #error "LASER_POWER_INLINE is obsolete."
598
 #elif defined(SPINDLE_LASER_PWM)
610
 #elif defined(SPINDLE_LASER_PWM)
599
   #error "SPINDLE_LASER_PWM (true) is now set with SPINDLE_LASER_USE_PWM (enabled)."
611
   #error "SPINDLE_LASER_PWM (true) is now set with SPINDLE_LASER_USE_PWM (enabled)."
600
 #elif ANY(IS_RAMPS_EEB, IS_RAMPS_EEF, IS_RAMPS_EFB, IS_RAMPS_EFF, IS_RAMPS_SF)
612
 #elif ANY(IS_RAMPS_EEB, IS_RAMPS_EEF, IS_RAMPS_EFB, IS_RAMPS_EFF, IS_RAMPS_SF)
3841
     #error "CUTTER_POWER_UNIT must be PWM255, PERCENT, RPM, or SERVO."
3853
     #error "CUTTER_POWER_UNIT must be PWM255, PERCENT, RPM, or SERVO."
3842
   #endif
3854
   #endif
3843
 
3855
 
3844
-  #if ENABLED(LASER_POWER_INLINE)
3856
+  #if ENABLED(LASER_FEATURE)
3845
     #if ENABLED(SPINDLE_CHANGE_DIR)
3857
     #if ENABLED(SPINDLE_CHANGE_DIR)
3846
-      #error "SPINDLE_CHANGE_DIR and LASER_POWER_INLINE are incompatible."
3847
-    #elif ENABLED(LASER_MOVE_G0_OFF) && DISABLED(LASER_MOVE_POWER)
3848
-      #error "LASER_MOVE_G0_OFF requires LASER_MOVE_POWER."
3858
+      #error "SPINDLE_CHANGE_DIR and LASER_FEATURE are incompatible."
3859
+    #elif ENABLED(LASER_MOVE_G0_OFF)
3860
+      #error "LASER_MOVE_G0_OFF is no longer required, G0 and G28 cannot apply power."
3861
+    #elif ENABLED(LASER_MOVE_G28_OFF)
3862
+      #error "LASER_MOVE_G0_OFF is no longer required, G0 and G28 cannot apply power."
3863
+    #elif ENABLED(LASER_MOVE_POWER)
3864
+      #error "LASER_MOVE_POWER is no longer applicable."
3849
     #endif
3865
     #endif
3850
-    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
3866
+    #if ENABLED(LASER_POWER_TRAP)
3851
       #if DISABLED(SPINDLE_LASER_USE_PWM)
3867
       #if DISABLED(SPINDLE_LASER_USE_PWM)
3852
-        #error "LASER_POWER_INLINE_TRAPEZOID requires SPINDLE_LASER_USE_PWM to function."
3853
-      #elif ENABLED(S_CURVE_ACCELERATION)
3854
-        //#ifndef LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
3855
-        //  #define LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
3856
-        //  #warning "Combining LASER_POWER_INLINE_TRAPEZOID with S_CURVE_ACCELERATION may result in unintended behavior."
3857
-        //#endif
3868
+        #error "LASER_POWER_TRAP requires SPINDLE_LASER_USE_PWM to function."
3858
       #endif
3869
       #endif
3859
     #endif
3870
     #endif
3860
-    #if ENABLED(LASER_POWER_INLINE_INVERT)
3861
-      //#ifndef LASER_POWER_INLINE_INVERT_WARN
3862
-      //  #define LASER_POWER_INLINE_INVERT_WARN
3863
-      //  #warning "Enabling LASER_POWER_INLINE_INVERT means that `M5` won't kill the laser immediately; use `M5 I` instead."
3864
-      //#endif
3865
-    #endif
3866
   #else
3871
   #else
3867
     #if SPINDLE_LASER_POWERUP_DELAY < 1
3872
     #if SPINDLE_LASER_POWERUP_DELAY < 1
3868
       #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
3873
       #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
3869
     #elif SPINDLE_LASER_POWERDOWN_DELAY < 1
3874
     #elif SPINDLE_LASER_POWERDOWN_DELAY < 1
3870
       #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
3875
       #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
3871
-    #elif ENABLED(LASER_MOVE_POWER)
3872
-      #error "LASER_MOVE_POWER requires LASER_POWER_INLINE."
3873
-    #elif ANY(LASER_POWER_INLINE_TRAPEZOID, LASER_POWER_INLINE_INVERT, LASER_MOVE_G0_OFF, LASER_MOVE_POWER)
3874
-      #error "Enabled an inline laser feature without inline laser power being enabled."
3875
     #endif
3876
     #endif
3876
   #endif
3877
   #endif
3877
 
3878
 
3889
       #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
3890
       #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
3890
     #elif !defined(SPINDLE_LASER_PWM_INVERT)
3891
     #elif !defined(SPINDLE_LASER_PWM_INVERT)
3891
       #error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE."
3892
       #error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE."
3892
-    #elif !(defined(SPEED_POWER_INTERCEPT) && defined(SPEED_POWER_MIN) && defined(SPEED_POWER_MAX) && defined(SPEED_POWER_STARTUP))
3893
+    #elif !(defined(SPEED_POWER_MIN) && defined(SPEED_POWER_MAX) && defined(SPEED_POWER_STARTUP))
3893
       #error "SPINDLE_LASER_USE_PWM equation constant(s) missing."
3894
       #error "SPINDLE_LASER_USE_PWM equation constant(s) missing."
3894
     #elif _PIN_CONFLICT(X_MIN)
3895
     #elif _PIN_CONFLICT(X_MIN)
3895
       #error "SPINDLE_LASER_USE_PWM pin conflicts with X_MIN_PIN."
3896
       #error "SPINDLE_LASER_USE_PWM pin conflicts with X_MIN_PIN."

+ 1
- 1
Marlin/src/lcd/dogm/status_screen_DOGM.cpp Целия файл

670
 
670
 
671
     // Laser / Spindle
671
     // Laser / Spindle
672
     #if DO_DRAW_CUTTER
672
     #if DO_DRAW_CUTTER
673
-      if (cutter.isReady && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
673
+      if (cutter.isReadyForUI && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
674
         #if CUTTER_UNIT_IS(PERCENT)
674
         #if CUTTER_UNIT_IS(PERCENT)
675
           lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, cutter_power2str(cutter.unitPower));
675
           lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, cutter_power2str(cutter.unitPower));
676
         #elif CUTTER_UNIT_IS(RPM)
676
         #elif CUTTER_UNIT_IS(RPM)

+ 5
- 0
Marlin/src/lcd/menu/menu_item.h Целия файл

27
 
27
 
28
 #include "../../inc/MarlinConfigPre.h"
28
 #include "../../inc/MarlinConfigPre.h"
29
 
29
 
30
+#if ENABLED(LASER_SYNCHRONOUS_M106_M107)
31
+  #include "../../module/planner.h"
32
+#endif
33
+
30
 void lcd_move_z();
34
 void lcd_move_z();
31
 
35
 
32
 ////////////////////////////////////////////
36
 ////////////////////////////////////////////
538
 
542
 
539
   inline void on_fan_update() {
543
   inline void on_fan_update() {
540
     thermalManager.set_fan_speed(MenuItemBase::itemIndex, editable.uint8);
544
     thermalManager.set_fan_speed(MenuItemBase::itemIndex, editable.uint8);
545
+    TERN_(LASER_SYNCHRONOUS_M106_M107, planner.buffer_sync_block(BLOCK_FLAG_SYNC_FANS));
541
   }
546
   }
542
 
547
 
543
   #if ENABLED(EXTRA_FAN_SPEED)
548
   #if ENABLED(EXTRA_FAN_SPEED)

+ 11
- 7
Marlin/src/lcd/menu/menu_spindle_laser.cpp Целия файл

33
   #include "../../feature/spindle_laser.h"
33
   #include "../../feature/spindle_laser.h"
34
 
34
 
35
   void menu_spindle_laser() {
35
   void menu_spindle_laser() {
36
-    bool is_enabled = cutter.enabled() && cutter.isReady;
36
+    bool is_enabled = cutter.enabled();
37
     #if ENABLED(SPINDLE_CHANGE_DIR)
37
     #if ENABLED(SPINDLE_CHANGE_DIR)
38
       bool is_rev = cutter.is_reverse();
38
       bool is_rev = cutter.is_reverse();
39
     #endif
39
     #endif
49
     #endif
49
     #endif
50
 
50
 
51
     editable.state = is_enabled;
51
     editable.state = is_enabled;
52
-    EDIT_ITEM(bool, MSG_CUTTER(TOGGLE), &is_enabled, []{ if (editable.state) cutter.disable(); else cutter.enable_same_dir(); });
52
+    EDIT_ITEM(bool, MSG_CUTTER(TOGGLE), &is_enabled, []{
53
+      #if ENABLED(SPINDLE_FEATURE)
54
+        if (editable.state) cutter.disable(); else cutter.enable_same_dir();
55
+      #else
56
+        cutter.laser_menu_toggle(!editable.state);
57
+      #endif
58
+    });
53
 
59
 
54
     #if ENABLED(AIR_EVACUATION)
60
     #if ENABLED(AIR_EVACUATION)
55
       bool evac_state = cutter.air_evac_state();
61
       bool evac_state = cutter.air_evac_state();
72
       // Setup and fire a test pulse using the current PWM power level for for a duration of test_pulse_min to test_pulse_max ms.
78
       // Setup and fire a test pulse using the current PWM power level for for a duration of test_pulse_min to test_pulse_max ms.
73
       EDIT_ITEM_FAST(CUTTER_MENU_PULSE_TYPE, MSG_LASER_PULSE_MS, &cutter.testPulse, LASER_TEST_PULSE_MIN, LASER_TEST_PULSE_MAX);
79
       EDIT_ITEM_FAST(CUTTER_MENU_PULSE_TYPE, MSG_LASER_PULSE_MS, &cutter.testPulse, LASER_TEST_PULSE_MIN, LASER_TEST_PULSE_MAX);
74
       ACTION_ITEM(MSG_LASER_FIRE_PULSE, cutter.test_fire_pulse);
80
       ACTION_ITEM(MSG_LASER_FIRE_PULSE, cutter.test_fire_pulse);
81
+      #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
82
+        EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 80000, cutter.refresh_frequency);
83
+      #endif
75
     #endif
84
     #endif
76
-
77
-    #if BOTH(MARLIN_DEV_MODE, HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
78
-      EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 80000, cutter.refresh_frequency);
79
-    #endif
80
-
81
     END_MENU();
85
     END_MENU();
82
   }
86
   }
83
 
87
 

+ 163
- 123
Marlin/src/module/planner.cpp Целия файл

128
 
128
 
129
 planner_settings_t Planner::settings;           // Initialized by settings.load()
129
 planner_settings_t Planner::settings;           // Initialized by settings.load()
130
 
130
 
131
-#if ENABLED(LASER_POWER_INLINE)
131
+/**
132
+ * Set up inline block variables
133
+ * Set laser_power_floor based on SPEED_POWER_MIN to pevent a zero power output state with LASER_POWER_TRAP
134
+ */
135
+#if ENABLED(LASER_FEATURE)
132
   laser_state_t Planner::laser_inline;          // Current state for blocks
136
   laser_state_t Planner::laser_inline;          // Current state for blocks
137
+  const uint8_t laser_power_floor = cutter.pct_to_ocr(SPEED_POWER_MIN);
133
 #endif
138
 #endif
134
 
139
 
135
 uint32_t Planner::max_acceleration_steps_per_s2[DISTINCT_AXES]; // (steps/s^2) Derived from mm_per_s2
140
 uint32_t Planner::max_acceleration_steps_per_s2[DISTINCT_AXES]; // (steps/s^2) Derived from mm_per_s2
799
   if (plateau_steps < 0) {
804
   if (plateau_steps < 0) {
800
     const float accelerate_steps_float = CEIL(intersection_distance(initial_rate, final_rate, accel, block->step_event_count));
805
     const float accelerate_steps_float = CEIL(intersection_distance(initial_rate, final_rate, accel, block->step_event_count));
801
     accelerate_steps = _MIN(uint32_t(_MAX(accelerate_steps_float, 0)), block->step_event_count);
806
     accelerate_steps = _MIN(uint32_t(_MAX(accelerate_steps_float, 0)), block->step_event_count);
807
+    decelerate_steps = block->step_event_count - accelerate_steps;
802
     plateau_steps = 0;
808
     plateau_steps = 0;
803
 
809
 
804
     #if ENABLED(S_CURVE_ACCELERATION)
810
     #if ENABLED(S_CURVE_ACCELERATION)
822
 
828
 
823
   // Store new block parameters
829
   // Store new block parameters
824
   block->accelerate_until = accelerate_steps;
830
   block->accelerate_until = accelerate_steps;
825
-  block->decelerate_after = accelerate_steps + plateau_steps;
831
+  block->decelerate_after = block->step_event_count - decelerate_steps;
826
   block->initial_rate = initial_rate;
832
   block->initial_rate = initial_rate;
827
   #if ENABLED(S_CURVE_ACCELERATION)
833
   #if ENABLED(S_CURVE_ACCELERATION)
828
     block->acceleration_time = acceleration_time;
834
     block->acceleration_time = acceleration_time;
833
   #endif
839
   #endif
834
   block->final_rate = final_rate;
840
   block->final_rate = final_rate;
835
 
841
 
836
-  /**
837
-   * Laser trapezoid calculations
838
-   *
839
-   * Approximate the trapezoid with the laser, incrementing the power every `entry_per` while accelerating
840
-   * and decrementing it every `exit_power_per` while decelerating, thus ensuring power is related to feedrate.
841
-   *
842
-   * LASER_POWER_INLINE_TRAPEZOID_CONT doesn't need this as it continuously approximates
843
-   *
844
-   * Note this may behave unreliably when running with S_CURVE_ACCELERATION
845
-   */
846
-  #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
847
-    if (block->laser.power > 0) { // No need to care if power == 0
848
-      const uint8_t entry_power = block->laser.power * entry_factor; // Power on block entry
849
-      #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
850
-        // Speedup power
851
-        const uint8_t entry_power_diff = block->laser.power - entry_power;
852
-        if (entry_power_diff) {
853
-          block->laser.entry_per = accelerate_steps / entry_power_diff;
854
-          block->laser.power_entry = entry_power;
855
-        }
856
-        else {
857
-          block->laser.entry_per = 0;
858
-          block->laser.power_entry = block->laser.power;
859
-        }
860
-        // Slowdown power
861
-        const uint8_t exit_power = block->laser.power * exit_factor, // Power on block entry
862
-                      exit_power_diff = block->laser.power - exit_power;
863
-        if (exit_power_diff) {
864
-          block->laser.exit_per = (block->step_event_count - block->decelerate_after) / exit_power_diff;
865
-          block->laser.power_exit = exit_power;
842
+  #if ENABLED(LASER_POWER_TRAP)
843
+    /**
844
+     * Laser Trapezoid Calculations
845
+     *
846
+     * Approximate the trapezoid with the laser, incrementing the power every `trap_ramp_entry_incr` steps while accelerating,
847
+     * and decrementing the power every `trap_ramp_exit_decr` while decelerating, to keep power proportional to feedrate.
848
+     * Laser power trap will reduce the initial power to no less than the laser_power_floor value. Based on the number
849
+     * of calculated accel/decel steps the power is distributed over the trapezoid entry- and exit-ramp steps.
850
+     *
851
+     * trap_ramp_active_pwr - The active power is initially set at a reduced level factor of initial power / accel steps and
852
+     * will be additively incremented using a trap_ramp_entry_incr value for each accel step processed later in the stepper code.
853
+     * The trap_ramp_exit_decr value is calculated as power / decel steps and is also adjusted to no less than the power floor.
854
+     *
855
+     * If the power == 0 the inline mode variables need to be set to zero to prevent stepper processing. The method allows
856
+     * for simpler non-powered moves like G0 or G28.
857
+     *
858
+     * Laser Trap Power works for all Jerk and Curve modes; however Arc-based moves will have issues since the segments are
859
+     * usually too small.
860
+     */
861
+    if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
862
+      if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
863
+        if (block->laser.power > 0) {
864
+          NOLESS(block->laser.power, laser_power_floor);
865
+          block->laser.trap_ramp_active_pwr = (block->laser.power - laser_power_floor) * (initial_rate / float(block->nominal_rate)) + laser_power_floor;
866
+          block->laser.trap_ramp_entry_incr = (block->laser.power - block->laser.trap_ramp_active_pwr) / accelerate_steps;
867
+          float laser_pwr = block->laser.power * (final_rate / float(block->nominal_rate));
868
+          NOLESS(laser_pwr, laser_power_floor);
869
+          block->laser.trap_ramp_exit_decr = (block->laser.power - laser_pwr) / decelerate_steps;
870
+          #if ENABLED(DEBUG_LASER_TRAP)
871
+            SERIAL_ECHO_MSG("lp:",block->laser.power);
872
+            SERIAL_ECHO_MSG("as:",accelerate_steps);
873
+            SERIAL_ECHO_MSG("ds:",decelerate_steps);
874
+            SERIAL_ECHO_MSG("p.trap:",block->laser.trap_ramp_active_pwr);
875
+            SERIAL_ECHO_MSG("p.incr:",block->laser.trap_ramp_entry_incr);
876
+            SERIAL_ECHO_MSG("p.decr:",block->laser.trap_ramp_exit_decr);
877
+          #endif
866
         }
878
         }
867
         else {
879
         else {
868
-          block->laser.exit_per = 0;
869
-          block->laser.power_exit = block->laser.power;
880
+          block->laser.trap_ramp_active_pwr = 0;
881
+          block->laser.trap_ramp_entry_incr = 0;
882
+          block->laser.trap_ramp_exit_decr = 0;
870
         }
883
         }
871
-      #else
872
-        block->laser.power_entry = entry_power;
873
-      #endif
884
+
885
+      }
874
     }
886
     }
875
-  #endif
887
+  #endif // LASER_POWER_TRAP
876
 }
888
 }
877
 
889
 
878
 /*                            PLANNER SPEED DEFINITION
890
 /*                            PLANNER SPEED DEFINITION
1130
   // The tail may be changed by the ISR so get a local copy.
1142
   // The tail may be changed by the ISR so get a local copy.
1131
   uint8_t block_index = block_buffer_tail,
1143
   uint8_t block_index = block_buffer_tail,
1132
           head_block_index = block_buffer_head;
1144
           head_block_index = block_buffer_head;
1133
-
1134
-  // Since there could be non-move blocks in the head of the queue, and the
1145
+  // Since there could be a sync block in the head of the queue, and the
1135
   // next loop must not recalculate the head block (as it needs to be
1146
   // next loop must not recalculate the head block (as it needs to be
1136
-  // specially handled), scan backwards to the first move block.
1147
+  // specially handled), scan backwards to the first non-SYNC block.
1137
   while (head_block_index != block_index) {
1148
   while (head_block_index != block_index) {
1138
 
1149
 
1139
     // Go back (head always point to the first free block)
1150
     // Go back (head always point to the first free block)
1203
   // Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated.
1214
   // Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated.
1204
   if (next) {
1215
   if (next) {
1205
 
1216
 
1206
-    // Mark the last block as RECALCULATE, to prevent the Stepper ISR running it.
1217
+    // Mark the next(last) block as RECALCULATE, to prevent the Stepper ISR running it.
1207
     // As the last block is always recalculated here, there is a chance the block isn't
1218
     // As the last block is always recalculated here, there is a chance the block isn't
1208
     // marked as RECALCULATE yet. That's the reason for the following line.
1219
     // marked as RECALCULATE yet. That's the reason for the following line.
1209
     block->flag.recalculate = true;
1220
     block->flag.recalculate = true;
1295
 #endif // HAS_FAN
1306
 #endif // HAS_FAN
1296
 
1307
 
1297
 /**
1308
 /**
1298
- * Maintain fans, paste extruder pressure,
1309
+ * Maintain fans, paste extruder pressure, spindle/laser power
1299
  */
1310
  */
1300
 void Planner::check_axes_activity() {
1311
 void Planner::check_axes_activity() {
1301
 
1312
 
1359
   }
1370
   }
1360
   else {
1371
   else {
1361
 
1372
 
1362
-    TERN_(HAS_CUTTER, cutter.refresh());
1373
+    TERN_(HAS_CUTTER, if (cutter.cutter_mode == CUTTER_MODE_STANDARD) cutter.refresh());
1363
 
1374
 
1364
     #if HAS_TAIL_FAN_SPEED
1375
     #if HAS_TAIL_FAN_SPEED
1365
       FANS_LOOP(i) {
1376
       FANS_LOOP(i) {
1459
     for (uint8_t b = block_buffer_tail; b != block_buffer_head; b = next_block_index(b)) {
1470
     for (uint8_t b = block_buffer_tail; b != block_buffer_head; b = next_block_index(b)) {
1460
       const block_t * const block = &block_buffer[b];
1471
       const block_t * const block = &block_buffer[b];
1461
       if (NUM_AXIS_GANG(block->steps.x, || block->steps.y, || block->steps.z, || block->steps.i, || block->steps.j, || block->steps.k, || block->steps.u, || block->steps.v, || block->steps.w)) {
1472
       if (NUM_AXIS_GANG(block->steps.x, || block->steps.y, || block->steps.z, || block->steps.i, || block->steps.j, || block->steps.k, || block->steps.u, || block->steps.v, || block->steps.w)) {
1462
-        const float se = (float)block->steps.e / block->step_event_count * SQRT(block->nominal_speed_sqr); // mm/sec
1473
+        const float se = (float)block->steps.e / block->step_event_count * SQRT(block->nominal_speed_sqr); // mm/sec;
1463
         NOLESS(high, se);
1474
         NOLESS(high, se);
1464
       }
1475
       }
1465
     }
1476
     }
1781
 bool Planner::_buffer_steps(const xyze_long_t &target
1792
 bool Planner::_buffer_steps(const xyze_long_t &target
1782
   OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
1793
   OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
1783
   OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
1794
   OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
1784
-  , feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters/*=0.0*/
1795
+  , feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters
1785
 ) {
1796
 ) {
1786
 
1797
 
1787
   // Wait for the next available block
1798
   // Wait for the next available block
1863
   );
1874
   );
1864
 
1875
 
1865
   /* <-- add a slash to enable
1876
   /* <-- add a slash to enable
1866
-    #define _ALINE(A) " " STR_##A  ":", target[_AXIS(A)], " (", int32_t(target[_AXIS(A)] - position[_AXIS(A)]), " steps)"
1867
-    SERIAL_ECHOLNPGM("  _populate_block FR:", fr_mm_s, LOGICAL_AXIS_MAP(_ALINE));
1877
+    SERIAL_ECHOLNPGM(
1878
+      "  _populate_block FR:", fr_mm_s,
1879
+      " A:", target.a, " (", da, " steps)"
1880
+      #if HAS_Y_AXIS
1881
+        " B:", target.b, " (", db, " steps)"
1882
+      #endif
1883
+      #if HAS_Z_AXIS
1884
+        " C:", target.c, " (", dc, " steps)"
1885
+      #endif
1886
+      #if HAS_I_AXIS
1887
+        " " STR_I ":", target.i, " (", di, " steps)"
1888
+      #endif
1889
+      #if HAS_J_AXIS
1890
+        " " STR_J ":", target.j, " (", dj, " steps)"
1891
+      #endif
1892
+      #if HAS_K_AXIS
1893
+        " " STR_K ":", target.k, " (", dk, " steps)"
1894
+      #endif
1895
+      #if HAS_U_AXIS
1896
+        " " STR_U ":", target.u, " (", du, " steps)"
1897
+      #endif
1898
+      #if HAS_V_AXIS
1899
+        " " STR_V ":", target.v, " (", dv, " steps)"
1900
+      #endif
1901
+      #if HAS_W_AXIS
1902
+        " " STR_W ":", target.w, " (", dw, " steps)"
1903
+      #if HAS_EXTRUDERS
1904
+        " E:", target.e, " (", de, " steps)"
1905
+      #endif
1906
+    );
1868
   //*/
1907
   //*/
1869
 
1908
 
1870
   #if EITHER(PREVENT_COLD_EXTRUSION, PREVENT_LENGTHY_EXTRUDE)
1909
   #if EITHER(PREVENT_COLD_EXTRUSION, PREVENT_LENGTHY_EXTRUDE)
1962
   // Set direction bits
2001
   // Set direction bits
1963
   block->direction_bits = dm;
2002
   block->direction_bits = dm;
1964
 
2003
 
1965
-  // Update block laser power
1966
-  #if ENABLED(LASER_POWER_INLINE)
1967
-    laser_inline.status.isPlanned = true;
1968
-    block->laser.status = laser_inline.status;
1969
-    block->laser.power = laser_inline.power;
2004
+  /**
2005
+   * Update block laser power
2006
+   * For standard mode get the cutter.power value for processing, since it's
2007
+   * only set by apply_power().
2008
+   */
2009
+  #if HAS_CUTTER
2010
+    switch (cutter.cutter_mode) {
2011
+      default: break;
2012
+
2013
+      case CUTTER_MODE_STANDARD: block->cutter_power = cutter.power; break;
2014
+
2015
+      #if ENABLED(LASER_FEATURE)
2016
+        /**
2017
+         * For inline mode get the laser_inline variables, including power and status.
2018
+         * Dynamic mode only needs to update if the feedrate has changed, since it's
2019
+         * calculated from the current feedrate and power level.
2020
+         */
2021
+        case CUTTER_MODE_CONTINUOUS:
2022
+          block->laser.power = laser_inline.power;
2023
+          block->laser.status = laser_inline.status;
2024
+          break;
2025
+
2026
+        case CUTTER_MODE_DYNAMIC:
2027
+          if (cutter.laser_feedrate_changed())  // Only process changes in rate
2028
+            block->laser.power = laser_inline.power = cutter.calc_dynamic_power();
2029
+          break;
2030
+      #endif
2031
+    }
1970
   #endif
2032
   #endif
1971
 
2033
 
1972
   // Number of steps for each axis
2034
   // Number of steps for each axis
2028
     #endif
2090
     #endif
2029
   #elif ENABLED(MARKFORGED_XY)
2091
   #elif ENABLED(MARKFORGED_XY)
2030
     steps_dist_mm.a      = (da - db) * mm_per_step[A_AXIS];
2092
     steps_dist_mm.a      = (da - db) * mm_per_step[A_AXIS];
2031
-    steps_dist_mm.b      =       db  * mm_per_step[B_AXIS];
2093
+    steps_dist_mm.b      = db * mm_per_step[B_AXIS];
2032
   #elif ENABLED(MARKFORGED_YX)
2094
   #elif ENABLED(MARKFORGED_YX)
2033
-    steps_dist_mm.a      =       da  * mm_per_step[A_AXIS];
2095
+    steps_dist_mm.a      = da * mm_per_step[A_AXIS];
2034
     steps_dist_mm.b      = (db - da) * mm_per_step[B_AXIS];
2096
     steps_dist_mm.b      = (db - da) * mm_per_step[B_AXIS];
2035
   #else
2097
   #else
2036
     XYZ_CODE(
2098
     XYZ_CODE(
2076
       block->millimeters = millimeters;
2138
       block->millimeters = millimeters;
2077
     else {
2139
     else {
2078
       /**
2140
       /**
2079
-       * Distance for interpretation of feedrate in accordance with LinuxCNC (the successor of
2080
-       * NIST RS274NGC interpreter - version 3) and its default CANON_XYZ feed reference mode.
2081
-       *
2082
-       * Assume:
2083
-       *   - X, Y, Z are the primary linear axes;
2084
-       *   - U, V, W are secondary linear axes;
2085
-       *   - A, B, C are rotational axes.
2086
-       *
2087
-       * Then:
2088
-       *   - dX, dY, dZ are the displacements of the primary linear axes;
2089
-       *   - dU, dV, dW are the displacements of linear axes;
2090
-       *   - dA, dB, dC are the displacements of rotational axes.
2091
-       *
2092
-       * The time it takes to execute move command with feedrate F is t = D/F,
2093
-       * where D is the total distance, calculated as follows:
2141
+       * Distance for interpretation of feedrate in accordance with LinuxCNC (the successor of NIST
2142
+       * RS274NGC interpreter - version 3) and its default CANON_XYZ feed reference mode.
2143
+       * Assume that X, Y, Z are the primary linear axes and U, V, W are secondary linear axes and A, B, C are
2144
+       * rotational axes. Then dX, dY, dZ are the displacements of the primary linear axes and dU, dV, dW are the displacements of linear axes and
2145
+       * dA, dB, dC are the displacements of rotational axes.
2146
+       * The time it takes to execute move command with feedrate F is t = D/F, where D is the total distance, calculated as follows:
2094
        *   D^2 = dX^2 + dY^2 + dZ^2
2147
        *   D^2 = dX^2 + dY^2 + dZ^2
2095
        *   if D^2 == 0 (none of XYZ move but any secondary linear axes move, whether other axes are moved or not):
2148
        *   if D^2 == 0 (none of XYZ move but any secondary linear axes move, whether other axes are moved or not):
2096
        *     D^2 = dU^2 + dV^2 + dW^2
2149
        *     D^2 = dU^2 + dV^2 + dW^2
2099
        */
2152
        */
2100
       float distance_sqr = (
2153
       float distance_sqr = (
2101
         #if ENABLED(ARTICULATED_ROBOT_ARM)
2154
         #if ENABLED(ARTICULATED_ROBOT_ARM)
2102
-          // For articulated robots, interpreting feedrate like LinuxCNC would require inverse kinematics. As a workaround,
2103
-          // assume that motors sit on a mutually-orthogonal axes and we can think of distance as magnitude of an n-vector
2104
-          // in an n-dimensional Euclidian space.
2155
+          // For articulated robots, interpreting feedrate like LinuxCNC would require inverse kinematics. As a workaround, pretend that motors sit on n mutually orthogonal
2156
+          // axes and assume that we could think of distance as magnitude of an n-vector in an n-dimensional Euclidian space.
2105
           NUM_AXIS_GANG(
2157
           NUM_AXIS_GANG(
2106
               sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z),
2158
               sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z),
2107
             + sq(steps_dist_mm.i), + sq(steps_dist_mm.j), + sq(steps_dist_mm.k),
2159
             + sq(steps_dist_mm.i), + sq(steps_dist_mm.j), + sq(steps_dist_mm.k),
2121
         #elif CORE_IS_YZ
2173
         #elif CORE_IS_YZ
2122
           XYZ_GANG(sq(steps_dist_mm.x),      + sq(steps_dist_mm.head.y), + sq(steps_dist_mm.head.z))
2174
           XYZ_GANG(sq(steps_dist_mm.x),      + sq(steps_dist_mm.head.y), + sq(steps_dist_mm.head.z))
2123
         #else
2175
         #else
2124
-          XYZ_GANG(sq(steps_dist_mm.x),      + sq(steps_dist_mm.y),      + sq(steps_dist_mm.z))
2176
+          XYZ_GANG(sq(steps_dist_mm.x),       + sq(steps_dist_mm.y),      + sq(steps_dist_mm.z))
2125
         #endif
2177
         #endif
2126
       );
2178
       );
2127
 
2179
 
2154
 
2206
 
2155
     /**
2207
     /**
2156
      * At this point at least one of the axes has more steps than
2208
      * At this point at least one of the axes has more steps than
2157
-     * MIN_STEPS_PER_SEGMENT, ensuring the segment won't get dropped
2158
-     * as zero-length. It's important to not apply corrections to blocks
2159
-     * that would get dropped!
2209
+     * MIN_STEPS_PER_SEGMENT, ensuring the segment won't get dropped as
2210
+     * zero-length. It's important to not apply corrections
2211
+     * to blocks that would get dropped!
2160
      *
2212
      *
2161
      * A correction function is permitted to add steps to an axis, it
2213
      * A correction function is permitted to add steps to an axis, it
2162
      * should *never* remove steps!
2214
      * should *never* remove steps!
2177
 
2229
 
2178
   TERN_(MIXING_EXTRUDER, mixer.populate_block(block->b_color));
2230
   TERN_(MIXING_EXTRUDER, mixer.populate_block(block->b_color));
2179
 
2231
 
2180
-  TERN_(HAS_CUTTER, block->cutter_power = cutter.power);
2181
 
2232
 
2182
   #if HAS_FAN
2233
   #if HAS_FAN
2183
     FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
2234
     FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
2192
 
2243
 
2193
   #if ENABLED(AUTO_POWER_CONTROL)
2244
   #if ENABLED(AUTO_POWER_CONTROL)
2194
     if (NUM_AXIS_GANG(
2245
     if (NUM_AXIS_GANG(
2195
-         block->steps.x, || block->steps.y, || block->steps.z,
2196
-      || block->steps.i, || block->steps.j, || block->steps.k,
2197
-      || block->steps.u, || block->steps.v, || block->steps.w
2246
+         block->steps.x,
2247
+      || block->steps.y,
2248
+      || block->steps.z,
2249
+      || block->steps.i,
2250
+      || block->steps.j,
2251
+      || block->steps.k,
2252
+      || block->steps.u,
2253
+      || block->steps.v,
2254
+      || block->steps.w
2198
     )) powerManager.power_on();
2255
     )) powerManager.power_on();
2199
   #endif
2256
   #endif
2200
 
2257
 
2428
   if (speed_factor < 1.0f) {
2485
   if (speed_factor < 1.0f) {
2429
     current_speed *= speed_factor;
2486
     current_speed *= speed_factor;
2430
     block->nominal_rate *= speed_factor;
2487
     block->nominal_rate *= speed_factor;
2431
-    block->nominal_speed_sqr *= sq(speed_factor);
2488
+    block->nominal_speed_sqr = block->nominal_speed_sqr * sq(speed_factor);
2432
   }
2489
   }
2433
 
2490
 
2434
   // Compute and limit the acceleration rate for the trapezoid generator.
2491
   // Compute and limit the acceleration rate for the trapezoid generator.
2630
         vmax_junction_sqr = sq(float(MINIMUM_PLANNER_SPEED));
2687
         vmax_junction_sqr = sq(float(MINIMUM_PLANNER_SPEED));
2631
       }
2688
       }
2632
       else {
2689
       else {
2690
+        NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
2691
+
2633
         // Convert delta vector to unit vector
2692
         // Convert delta vector to unit vector
2634
         xyze_float_t junction_unit_vec = unit_vec - prev_unit_vec;
2693
         xyze_float_t junction_unit_vec = unit_vec - prev_unit_vec;
2635
         normalize_junction_vector(junction_unit_vec);
2694
         normalize_junction_vector(junction_unit_vec);
2636
 
2695
 
2637
-        const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec);
2638
-
2639
-        NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
2640
-
2641
-        const float sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
2696
+        const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec),
2697
+                    sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
2642
 
2698
 
2643
         vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2);
2699
         vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2);
2644
 
2700
 
2889
 
2945
 
2890
 /**
2946
 /**
2891
  * Planner::buffer_sync_block
2947
  * Planner::buffer_sync_block
2892
- * Add a block to the buffer that just updates the position,
2893
- * or in case of LASER_SYNCHRONOUS_M106_M107 the fan PWM
2948
+ * Add a block to the buffer that just updates the position
2949
+ * @param sync_flag BLOCK_FLAG_SYNC_FANS & BLOCK_FLAG_LASER_PWR
2950
+ * Supports LASER_SYNCHRONOUS_M106_M107 and LASER_POWER_SYNC power sync block buffer queueing.
2894
  */
2951
  */
2895
-void Planner::buffer_sync_block(TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/)) {
2896
-  #if DISABLED(LASER_SYNCHRONOUS_M106_M107)
2897
-    constexpr BlockFlagBit sync_flag = BLOCK_BIT_SYNC_POSITION;
2898
-  #endif
2952
+
2953
+void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/) {
2899
 
2954
 
2900
   // Wait for the next available block
2955
   // Wait for the next available block
2901
   uint8_t next_buffer_head;
2956
   uint8_t next_buffer_head;
2902
   block_t * const block = get_next_free_block(next_buffer_head);
2957
   block_t * const block = get_next_free_block(next_buffer_head);
2903
 
2958
 
2904
   // Clear block
2959
   // Clear block
2905
-  block->reset();
2906
-
2960
+  memset(block, 0, sizeof(block_t));
2907
   block->flag.apply(sync_flag);
2961
   block->flag.apply(sync_flag);
2908
 
2962
 
2909
   block->position = position;
2963
   block->position = position;
2915
     FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
2969
     FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
2916
   #endif
2970
   #endif
2917
 
2971
 
2972
+  /**
2973
+   * M3-based power setting can be processed inline with a laser power sync block.
2974
+   * During active moves cutter.power is processed immediately, otherwise on the next move.
2975
+   */
2976
+  TERN_(LASER_POWER_SYNC, block->laser.power = cutter.power);
2977
+
2918
   // If this is the first added movement, reload the delay, otherwise, cancel it.
2978
   // If this is the first added movement, reload the delay, otherwise, cancel it.
2919
   if (block_buffer_head == block_buffer_tail) {
2979
   if (block_buffer_head == block_buffer_tail) {
2920
     // If it was the first queued block, restart the 1st block delivery delay, to
2980
     // If it was the first queued block, restart the 1st block delivery delay, to
3052
   if (!_buffer_steps(target
3112
   if (!_buffer_steps(target
3053
       OPTARG(HAS_POSITION_FLOAT, target_float)
3113
       OPTARG(HAS_POSITION_FLOAT, target_float)
3054
       OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
3114
       OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
3055
-      , fr_mm_s, extruder, millimeters
3056
-  )) return false;
3115
+      , fr_mm_s, extruder, millimeters)
3116
+  ) return false;
3057
 
3117
 
3058
   stepper.wake_up();
3118
   stepper.wake_up();
3059
   return true;
3119
   return true;
3099
     inverse_kinematics(machine);
3159
     inverse_kinematics(machine);
3100
 
3160
 
3101
     #if ENABLED(SCARA_FEEDRATE_SCALING)
3161
     #if ENABLED(SCARA_FEEDRATE_SCALING)
3102
-      // For SCARA scale the feed rate from mm/s to degrees/s
3162
+      // For SCARA scale the feedrate from mm/s to degrees/s
3103
       // i.e., Complete the angular vector in the given time.
3163
       // i.e., Complete the angular vector in the given time.
3104
       const float duration_recip = inv_duration ?: fr_mm_s / mm;
3164
       const float duration_recip = inv_duration ?: fr_mm_s / mm;
3105
       const xyz_pos_t diff = delta - position_float;
3165
       const xyz_pos_t diff = delta - position_float;
3120
 
3180
 
3121
 #if ENABLED(DIRECT_STEPPING)
3181
 #if ENABLED(DIRECT_STEPPING)
3122
 
3182
 
3123
-  /**
3124
-   * @brief Add a direct stepping page block to the buffer
3125
-   *        and wake up the Stepper ISR to process it.
3126
-   *
3127
-   * @param page_idx Page index provided by G6 I<index>
3128
-   * @param extruder The extruder to use in the move
3129
-   * @param num_steps Number of steps to process in the ISR
3130
-   */
3131
   void Planner::buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps) {
3183
   void Planner::buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps) {
3132
     if (!last_page_step_rate) {
3184
     if (!last_page_step_rate) {
3133
       kill(GET_TEXT_F(MSG_BAD_PAGE_SPEED));
3185
       kill(GET_TEXT_F(MSG_BAD_PAGE_SPEED));
3212
   if (has_blocks_queued()) {
3264
   if (has_blocks_queued()) {
3213
     //previous_nominal_speed_sqr = 0.0; // Reset planner junction speeds. Assume start from rest.
3265
     //previous_nominal_speed_sqr = 0.0; // Reset planner junction speeds. Assume start from rest.
3214
     //previous_speed.reset();
3266
     //previous_speed.reset();
3215
-    buffer_sync_block();
3267
+    buffer_sync_block(BLOCK_BIT_SYNC_POSITION);
3216
   }
3268
   }
3217
   else {
3269
   else {
3218
     #if ENABLED(BACKLASH_COMPENSATION)
3270
     #if ENABLED(BACKLASH_COMPENSATION)
3225
   }
3277
   }
3226
 }
3278
 }
3227
 
3279
 
3228
-/**
3229
- * @brief Set the Planner position in mm
3230
- * @details Set the Planner position from a native machine position in mm
3231
- *
3232
- * @param xyze A native (Cartesian) machine position
3233
- */
3234
 void Planner::set_position_mm(const xyze_pos_t &xyze) {
3280
 void Planner::set_position_mm(const xyze_pos_t &xyze) {
3235
   xyze_pos_t machine = xyze;
3281
   xyze_pos_t machine = xyze;
3236
   TERN_(HAS_POSITION_MODIFIERS, apply_modifiers(machine, true));
3282
   TERN_(HAS_POSITION_MODIFIERS, apply_modifiers(machine, true));
3259
     TERN_(IS_KINEMATIC, TERN_(HAS_EXTRUDERS, position_cart.e = e));
3305
     TERN_(IS_KINEMATIC, TERN_(HAS_EXTRUDERS, position_cart.e = e));
3260
 
3306
 
3261
     if (has_blocks_queued())
3307
     if (has_blocks_queued())
3262
-      buffer_sync_block();
3308
+      buffer_sync_block(BLOCK_BIT_SYNC_POSITION);
3263
     else
3309
     else
3264
       stepper.set_axis_position(E_AXIS, position.e);
3310
       stepper.set_axis_position(E_AXIS, position.e);
3265
   }
3311
   }
3266
 
3312
 
3267
 #endif
3313
 #endif
3268
 
3314
 
3269
-/**
3270
- * @brief Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
3271
- * @details Update planner movement factors after a change to certain settings:
3272
- *          - max_acceleration_steps_per_s2 from settings max_acceleration_mm_per_s2 * axis_steps_per_mm (M201, M92)
3273
- *          - acceleration_long_cutoff based on the largest max_acceleration_steps_per_s2 (M201)
3274
- *          - max_e_jerk for all extruders based on junction_deviation_mm (M205 J)
3275
- */
3315
+// Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
3276
 void Planner::refresh_acceleration_rates() {
3316
 void Planner::refresh_acceleration_rates() {
3277
   uint32_t highest_rate = 1;
3317
   uint32_t highest_rate = 1;
3278
   LOOP_DISTINCT_AXES(i) {
3318
   LOOP_DISTINCT_AXES(i) {
3285
 }
3325
 }
3286
 
3326
 
3287
 /**
3327
 /**
3288
- * @brief Recalculate 'position' and 'mm_per_step'.
3289
- * @details Required whenever settings.axis_steps_per_mm changes!
3328
+ * Recalculate 'position' and 'mm_per_step'.
3329
+ * Must be called whenever settings.axis_steps_per_mm changes!
3290
  */
3330
  */
3291
 void Planner::refresh_positioning() {
3331
 void Planner::refresh_positioning() {
3292
   LOOP_DISTINCT_AXES(i) mm_per_step[i] = 1.0f / settings.axis_steps_per_mm[i];
3332
   LOOP_DISTINCT_AXES(i) mm_per_step[i] = 1.0f / settings.axis_steps_per_mm[i];

+ 47
- 45
Marlin/src/module/planner.h Целия файл

89
   #define HAS_DIST_MM_ARG 1
89
   #define HAS_DIST_MM_ARG 1
90
 #endif
90
 #endif
91
 
91
 
92
-#if ENABLED(LASER_POWER_INLINE)
93
-
94
-  typedef struct {
95
-    bool isPlanned:1;
96
-    bool isEnabled:1;
97
-    bool dir:1;
98
-    bool Reserved:6;
99
-  } power_status_t;
100
-
101
-  typedef struct {
102
-    power_status_t status;    // See planner settings for meaning
103
-    uint8_t power;            // Ditto; When in trapezoid mode this is nominal power
104
-    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
105
-      uint8_t   power_entry;  // Entry power for the laser
106
-      #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
107
-        uint8_t   power_exit; // Exit power for the laser
108
-        uint32_t  entry_per,  // Steps per power increment (to avoid floats in stepper calcs)
109
-                  exit_per;   // Steps per power decrement
110
-      #endif
111
-    #endif
112
-  } block_laser_t;
113
-
114
-#endif
115
-
116
 /**
92
 /**
117
  * Planner block flags as boolean bit fields
93
  * Planner block flags as boolean bit fields
118
  */
94
  */
132
   BLOCK_BIT_SYNC_POSITION
108
   BLOCK_BIT_SYNC_POSITION
133
 
109
 
134
   // Direct stepping page
110
   // Direct stepping page
135
-  #if ENABLED(DIRECT_STEPPING)
136
-    , BLOCK_BIT_PAGE
137
-  #endif
111
+  OPTARG(DIRECT_STEPPING, BLOCK_BIT_PAGE)
112
+
138
 
113
 
139
   // Sync the fan speeds from the block
114
   // Sync the fan speeds from the block
140
-  #if ENABLED(LASER_SYNCHRONOUS_M106_M107)
141
-    , BLOCK_BIT_SYNC_FANS
142
-  #endif
115
+  OPTARG(LASER_SYNCHRONOUS_M106_M107, BLOCK_BIT_SYNC_FANS)
116
+
117
+  // Sync laser power from a queued block
118
+  OPTARG(LASER_POWER_SYNC, BLOCK_BIT_LASER_PWR)
143
 };
119
 };
144
 
120
 
145
 /**
121
 /**
165
       #if ENABLED(LASER_SYNCHRONOUS_M106_M107)
141
       #if ENABLED(LASER_SYNCHRONOUS_M106_M107)
166
         bool sync_fans:1;
142
         bool sync_fans:1;
167
       #endif
143
       #endif
144
+
145
+      #if ENABLED(LASER_POWER_SYNC)
146
+        bool sync_laser_pwr:1;
147
+      #endif
168
     };
148
     };
169
   };
149
   };
170
 
150
 
176
 
156
 
177
 } block_flags_t;
157
 } block_flags_t;
178
 
158
 
159
+#if ENABLED(LASER_FEATURE)
160
+
161
+  typedef struct {
162
+    bool isEnabled:1;                                 // Set to engage the inline laser power output.
163
+    bool dir:1;
164
+    bool isPowered:1;                                 // Set on any parsed G1, G2, G3, or G5 powered move, cleared on G0 and G28.
165
+    bool isSyncPower:1;                               // Set on a M3 sync based set laser power, used to determine active trap power
166
+    bool Reserved:4;
167
+  } power_status_t;
168
+
169
+  typedef struct {
170
+    power_status_t status;                            // See planner settings for meaning
171
+    uint8_t power;                                    // Ditto; When in trapezoid mode this is nominal power
172
+
173
+    #if ENABLED(LASER_POWER_TRAP)
174
+      float trap_ramp_active_pwr;                     // Laser power level during active trapezoid smoothing
175
+      float trap_ramp_entry_incr;                     // Acceleration per step laser power increment (trap entry)
176
+      float trap_ramp_exit_decr;                      // Deceleration per step laser power decrement (trap exit)
177
+    #endif
178
+  } block_laser_t;
179
+
180
+#endif
181
+
179
 /**
182
 /**
180
- * A single entry in the planner buffer, used to set up and
181
- * track a coordinated linear motion for one or more axes.
183
+ * struct block_t
184
+ *
185
+ * A single entry in the planner buffer.
186
+ * Tracks linear movement over multiple axes.
182
  *
187
  *
183
  * The "nominal" values are as-specified by G-code, and
188
  * The "nominal" values are as-specified by G-code, and
184
  * may never actually be reached due to acceleration limits.
189
  * may never actually be reached due to acceleration limits.
188
   volatile block_flags_t flag;              // Block flags
193
   volatile block_flags_t flag;              // Block flags
189
 
194
 
190
   volatile bool is_fan_sync() { return TERN0(LASER_SYNCHRONOUS_M106_M107, flag.sync_fans); }
195
   volatile bool is_fan_sync() { return TERN0(LASER_SYNCHRONOUS_M106_M107, flag.sync_fans); }
191
-  volatile bool is_sync() { return flag.sync_position || is_fan_sync(); }
196
+  volatile bool is_pwr_sync() { return TERN0(LASER_POWER_SYNC, flag.sync_laser_pwr); }
197
+  volatile bool is_sync() { return flag.sync_position || is_fan_sync() || is_pwr_sync(); }
192
   volatile bool is_page() { return TERN0(DIRECT_STEPPING, flag.page); }
198
   volatile bool is_page() { return TERN0(DIRECT_STEPPING, flag.page); }
193
   volatile bool is_move() { return !(is_sync() || is_page()); }
199
   volatile bool is_move() { return !(is_sync() || is_page()); }
194
 
200
 
270
     xyze_pos_t start_position;
276
     xyze_pos_t start_position;
271
   #endif
277
   #endif
272
 
278
 
273
-  #if ENABLED(LASER_POWER_INLINE)
279
+  #if ENABLED(LASER_FEATURE)
274
     block_laser_t laser;
280
     block_laser_t laser;
275
   #endif
281
   #endif
276
 
282
 
277
-  void reset() { memset((char*)this, 0, sizeof(*this)); }
278
-
279
 } block_t;
283
 } block_t;
280
 
284
 
281
 #if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
285
 #if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
284
 
288
 
285
 #define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
289
 #define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
286
 
290
 
287
-#if ENABLED(LASER_POWER_INLINE)
291
+#if ENABLED(LASER_FEATURE)
288
   typedef struct {
292
   typedef struct {
289
     /**
293
     /**
290
      * Laser status flags
294
      * Laser status flags
293
     /**
297
     /**
294
      * Laser power: 0 or 255 in case of PWM-less laser,
298
      * Laser power: 0 or 255 in case of PWM-less laser,
295
      * or the OCR (oscillator count register) value;
299
      * or the OCR (oscillator count register) value;
296
-     *
297
      * Using OCR instead of raw power, because it avoids
300
      * Using OCR instead of raw power, because it avoids
298
      * floating point operations during the move loop.
301
      * floating point operations during the move loop.
299
      */
302
      */
300
-    uint8_t power;
303
+    volatile uint8_t power;
301
   } laser_state_t;
304
   } laser_state_t;
302
 #endif
305
 #endif
303
 
306
 
399
 
402
 
400
     static planner_settings_t settings;
403
     static planner_settings_t settings;
401
 
404
 
402
-    #if ENABLED(LASER_POWER_INLINE)
405
+    #if ENABLED(LASER_FEATURE)
403
       static laser_state_t laser_inline;
406
       static laser_state_t laser_inline;
404
     #endif
407
     #endif
405
 
408
 
784
 
787
 
785
     /**
788
     /**
786
      * Planner::buffer_sync_block
789
      * Planner::buffer_sync_block
787
-     * Add a block to the buffer that just updates the position or in
788
-     * case of LASER_SYNCHRONOUS_M106_M107 the fan pwm
790
+     * Add a block to the buffer that just updates the position
791
+     * @param sync_flag sets a condition bit to process additional items
792
+     * such as sync fan pwm or sync M3/M4 laser power into a queued block
789
      */
793
      */
790
-    static void buffer_sync_block(
791
-      TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFlagBit flag=BLOCK_BIT_SYNC_POSITION)
792
-    );
794
+      static void buffer_sync_block(const BlockFlagBit flag=BLOCK_BIT_SYNC_POSITION);
793
 
795
 
794
   #if IS_KINEMATIC
796
   #if IS_KINEMATIC
795
     private:
797
     private:

+ 105
- 131
Marlin/src/module/stepper.cpp Целия файл

253
 xyze_long_t Stepper::count_position{0};
253
 xyze_long_t Stepper::count_position{0};
254
 xyze_int8_t Stepper::count_direction{0};
254
 xyze_int8_t Stepper::count_direction{0};
255
 
255
 
256
-#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
257
-  Stepper::stepper_laser_t Stepper::laser_trap = {
258
-    .enabled = false,
259
-    .cur_power = 0,
260
-    .cruise_set = false,
261
-    #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
262
-      .last_step_count = 0,
263
-      .acc_step_count = 0
264
-    #else
265
-      .till_update = 0
266
-    #endif
267
-  };
268
-#endif
269
-
270
 #define MINDIR(A) (count_direction[_AXIS(A)] < 0)
256
 #define MINDIR(A) (count_direction[_AXIS(A)] < 0)
271
 #define MAXDIR(A) (count_direction[_AXIS(A)] > 0)
257
 #define MAXDIR(A) (count_direction[_AXIS(A)] > 0)
272
 
258
 
1964
 
1950
 
1965
   // If there is a current block
1951
   // If there is a current block
1966
   if (current_block) {
1952
   if (current_block) {
1967
-
1968
     // If current block is finished, reset pointer and finalize state
1953
     // If current block is finished, reset pointer and finalize state
1969
     if (step_events_completed >= step_event_count) {
1954
     if (step_events_completed >= step_event_count) {
1970
       #if ENABLED(DIRECT_STEPPING)
1955
       #if ENABLED(DIRECT_STEPPING)
2017
           else if (LA_steps) nextAdvanceISR = 0;
2002
           else if (LA_steps) nextAdvanceISR = 0;
2018
         #endif
2003
         #endif
2019
 
2004
 
2020
-        // Update laser - Accelerating
2021
-        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
2022
-          if (laser_trap.enabled) {
2023
-            #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
2024
-              if (current_block->laser.entry_per) {
2025
-                laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count;
2026
-                laser_trap.last_step_count = step_events_completed;
2027
-
2028
-                // Should be faster than a divide, since this should trip just once
2029
-                if (laser_trap.acc_step_count < 0) {
2030
-                  while (laser_trap.acc_step_count < 0) {
2031
-                    laser_trap.acc_step_count += current_block->laser.entry_per;
2032
-                    if (laser_trap.cur_power < current_block->laser.power) laser_trap.cur_power++;
2033
-                  }
2034
-                  cutter.ocr_set_power(laser_trap.cur_power);
2035
-                }
2036
-              }
2037
-            #else
2038
-              if (laser_trap.till_update)
2039
-                laser_trap.till_update--;
2040
-              else {
2041
-                laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
2042
-                laser_trap.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate;
2043
-                cutter.ocr_set_power(laser_trap.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles
2005
+        /*
2006
+         * Adjust Laser Power - Accelerating
2007
+         * isPowered - True when a move is powered.
2008
+         * isEnabled - laser power is active.
2009
+         * Laser power variables are calulated and stored in this block by the planner code.
2010
+         *
2011
+         * trap_ramp_active_pwr - the active power in this block across accel or decel trap steps.
2012
+         * trap_ramp_entry_incr - holds the precalculated value to increase the current power per accel step.
2013
+         *
2014
+         * Apply the starting active power and then increase power per step by the trap_ramp_entry_incr value if positive.
2015
+         */
2016
+
2017
+        #if ENABLED(LASER_POWER_TRAP)
2018
+          if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
2019
+            if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
2020
+              if (current_block->laser.trap_ramp_entry_incr > 0) {
2021
+                cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
2022
+                current_block->laser.trap_ramp_active_pwr += current_block->laser.trap_ramp_entry_incr;
2044
               }
2023
               }
2045
-            #endif
2024
+            }
2025
+            // Not a powered move.
2026
+            else cutter.apply_power(0);
2046
           }
2027
           }
2047
         #endif
2028
         #endif
2048
       }
2029
       }
2066
               : current_block->final_rate;
2047
               : current_block->final_rate;
2067
           }
2048
           }
2068
         #else
2049
         #else
2069
-
2070
           // Using the old trapezoidal control
2050
           // Using the old trapezoidal control
2071
           step_rate = STEP_MULTIPLY(deceleration_time, current_block->acceleration_rate);
2051
           step_rate = STEP_MULTIPLY(deceleration_time, current_block->acceleration_rate);
2072
           if (step_rate < acc_step_rate) { // Still decelerating?
2052
           if (step_rate < acc_step_rate) { // Still decelerating?
2094
           else if (LA_steps) nextAdvanceISR = 0;
2074
           else if (LA_steps) nextAdvanceISR = 0;
2095
         #endif // LIN_ADVANCE
2075
         #endif // LIN_ADVANCE
2096
 
2076
 
2097
-        // Update laser - Decelerating
2098
-        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
2099
-          if (laser_trap.enabled) {
2100
-            #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
2101
-              if (current_block->laser.exit_per) {
2102
-                laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count;
2103
-                laser_trap.last_step_count = step_events_completed;
2104
-
2105
-                // Should be faster than a divide, since this should trip just once
2106
-                if (laser_trap.acc_step_count < 0) {
2107
-                  while (laser_trap.acc_step_count < 0) {
2108
-                    laser_trap.acc_step_count += current_block->laser.exit_per;
2109
-                    if (laser_trap.cur_power > current_block->laser.power_exit) laser_trap.cur_power--;
2110
-                  }
2111
-                  cutter.ocr_set_power(laser_trap.cur_power);
2112
-                }
2113
-              }
2114
-            #else
2115
-              if (laser_trap.till_update)
2116
-                laser_trap.till_update--;
2117
-              else {
2118
-                laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
2119
-                laser_trap.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate;
2120
-                cutter.ocr_set_power(laser_trap.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles
2077
+        /*
2078
+         * Adjust Laser Power - Decelerating
2079
+         * trap_ramp_entry_decr - holds the precalculated value to decrease the current power per decel step.
2080
+         */
2081
+        #if ENABLED(LASER_POWER_TRAP)
2082
+          if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
2083
+            if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
2084
+              if (current_block->laser.trap_ramp_exit_decr > 0) {
2085
+                current_block->laser.trap_ramp_active_pwr -= current_block->laser.trap_ramp_exit_decr;
2086
+                cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
2121
               }
2087
               }
2122
-            #endif
2088
+              // Not a powered move.
2089
+              else cutter.apply_power(0);
2090
+            }
2123
           }
2091
           }
2124
         #endif
2092
         #endif
2093
+
2125
       }
2094
       }
2126
-      // Must be in cruise phase otherwise
2127
-      else {
2095
+      else {  // Must be in cruise phase otherwise
2128
 
2096
 
2129
         #if ENABLED(LIN_ADVANCE)
2097
         #if ENABLED(LIN_ADVANCE)
2130
           // If there are any esteps, fire the next advance_isr "now"
2098
           // If there are any esteps, fire the next advance_isr "now"
2139
 
2107
 
2140
         // The timer interval is just the nominal value for the nominal speed
2108
         // The timer interval is just the nominal value for the nominal speed
2141
         interval = ticks_nominal;
2109
         interval = ticks_nominal;
2110
+      }
2142
 
2111
 
2143
-        // Update laser - Cruising
2144
-        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
2145
-          if (laser_trap.enabled) {
2146
-            if (!laser_trap.cruise_set) {
2147
-              laser_trap.cur_power = current_block->laser.power;
2148
-              cutter.ocr_set_power(laser_trap.cur_power);
2149
-              laser_trap.cruise_set = true;
2112
+      /* Adjust Laser Power - Cruise
2113
+       * power - direct or floor adjusted active laser power.
2114
+       */
2115
+
2116
+      #if ENABLED(LASER_POWER_TRAP)
2117
+        if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
2118
+          if (step_events_completed + 1 == accelerate_until) {
2119
+            if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
2120
+              if (current_block->laser.trap_ramp_entry_incr > 0) {
2121
+                current_block->laser.trap_ramp_active_pwr = current_block->laser.power;
2122
+                cutter.apply_power(current_block->laser.power);
2123
+              }
2150
             }
2124
             }
2151
-            #if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
2152
-              laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
2153
-            #else
2154
-              laser_trap.last_step_count = step_events_completed;
2155
-            #endif
2125
+            // Not a powered move.
2126
+            else cutter.apply_power(0);
2156
           }
2127
           }
2157
-        #endif
2158
-      }
2128
+        }
2129
+      #endif
2159
     }
2130
     }
2131
+
2132
+    #if ENABLED(LASER_FEATURE)
2133
+      /*
2134
+       * CUTTER_MODE_DYNAMIC is experimental and developing.
2135
+       * Super-fast method to dynamically adjust the laser power OCR value based on the input feedrate in mm-per-minute.
2136
+       * TODO: Set up Min/Max OCR offsets to allow tuning and scaling of various lasers.
2137
+       * TODO: Integrate accel/decel +-rate into the dynamic laser power calc.
2138
+       */
2139
+      if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC
2140
+        && planner.laser_inline.status.isPowered                  // isPowered flag set on any parsed G1, G2, G3, or G5 move; cleared on any others.
2141
+        && cutter.last_block_power != current_block->laser.power  // Prevent constant update without change
2142
+      ) {
2143
+        cutter.apply_power(current_block->laser.power);
2144
+        cutter.last_block_power = current_block->laser.power;
2145
+      }
2146
+    #endif
2147
+  }
2148
+  else { // !current_block
2149
+    #if ENABLED(LASER_FEATURE)
2150
+      if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC) {
2151
+        cutter.apply_power(0);  // No movement in dynamic mode so turn Laser off
2152
+      }
2153
+    #endif
2160
   }
2154
   }
2161
 
2155
 
2162
   // If there is no current block at this point, attempt to pop one from the buffer
2156
   // If there is no current block at this point, attempt to pop one from the buffer
2169
       // Sync block? Sync the stepper counts or fan speeds and return
2163
       // Sync block? Sync the stepper counts or fan speeds and return
2170
       while (current_block->is_sync()) {
2164
       while (current_block->is_sync()) {
2171
 
2165
 
2172
-        if (current_block->is_fan_sync()) {
2173
-          TERN_(LASER_SYNCHRONOUS_M106_M107, planner.sync_fan_speeds(current_block->fan_speed));
2174
-        }
2175
-        else
2176
-          _set_position(current_block->position);
2166
+        #if ENABLED(LASER_POWER_SYNC)
2167
+          if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
2168
+            if (current_block->is_pwr_sync()) {
2169
+              planner.laser_inline.status.isSyncPower = true;
2170
+              cutter.apply_power(current_block->laser.power);
2171
+            }
2172
+          }
2173
+        #endif
2174
+
2175
+        TERN_(LASER_SYNCHRONOUS_M106_M107, if (current_block->is_fan_sync()) planner.sync_fan_speeds(current_block->fan_speed));
2176
+
2177
+        if (!(current_block->is_fan_sync() || current_block->is_pwr_sync())) _set_position(current_block->position);
2177
 
2178
 
2178
         discard_current_block();
2179
         discard_current_block();
2179
 
2180
 
2183
       }
2184
       }
2184
 
2185
 
2185
       // For non-inline cutter, grossly apply power
2186
       // For non-inline cutter, grossly apply power
2186
-      #if ENABLED(LASER_FEATURE) && DISABLED(LASER_POWER_INLINE)
2187
-        cutter.apply_power(current_block->cutter_power);
2187
+      #if HAS_CUTTER
2188
+        if (cutter.cutter_mode == CUTTER_MODE_STANDARD) {
2189
+          cutter.apply_power(current_block->cutter_power);
2190
+        }
2188
       #endif
2191
       #endif
2189
 
2192
 
2190
       #if ENABLED(POWER_LOSS_RECOVERY)
2193
       #if ENABLED(POWER_LOSS_RECOVERY)
2357
         set_directions(current_block->direction_bits);
2360
         set_directions(current_block->direction_bits);
2358
       }
2361
       }
2359
 
2362
 
2360
-      #if ENABLED(LASER_POWER_INLINE)
2361
-        const power_status_t stat = current_block->laser.status;
2362
-        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
2363
-          laser_trap.enabled = stat.isPlanned && stat.isEnabled;
2364
-          laser_trap.cur_power = current_block->laser.power_entry; // RESET STATE
2365
-          laser_trap.cruise_set = false;
2366
-          #if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
2367
-            laser_trap.last_step_count = 0;
2368
-            laser_trap.acc_step_count = current_block->laser.entry_per / 2;
2369
-          #else
2370
-            laser_trap.till_update = 0;
2371
-          #endif
2372
-          // Always have PWM in this case
2373
-          if (stat.isPlanned) {                        // Planner controls the laser
2374
-            cutter.ocr_set_power(
2375
-              stat.isEnabled ? laser_trap.cur_power : 0 // ON with power or OFF
2376
-            );
2377
-          }
2378
-        #else
2379
-          if (stat.isPlanned) {                        // Planner controls the laser
2380
-            #if ENABLED(SPINDLE_LASER_USE_PWM)
2381
-              cutter.ocr_set_power(
2382
-                stat.isEnabled ? current_block->laser.power : 0 // ON with power or OFF
2383
-              );
2363
+      #if ENABLED(LASER_FEATURE)
2364
+        if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {           // Planner controls the laser
2365
+          if (planner.laser_inline.status.isSyncPower)
2366
+            // If the previous block was a M3 sync power then skip the trap power init otherwise it will 0 the sync power.
2367
+            planner.laser_inline.status.isSyncPower = false;          // Clear the flag to process subsequent trap calc's.
2368
+          else if (current_block->laser.status.isEnabled) {
2369
+            #if ENABLED(LASER_POWER_TRAP)
2370
+              TERN_(DEBUG_LASER_TRAP, SERIAL_ECHO_MSG("InitTrapPwr:",current_block->laser.trap_ramp_active_pwr));
2371
+              cutter.apply_power(current_block->laser.status.isPowered ? current_block->laser.trap_ramp_active_pwr : 0);
2384
             #else
2372
             #else
2385
-              cutter.set_enabled(stat.isEnabled);
2373
+              TERN_(DEBUG_CUTTER_POWER, SERIAL_ECHO_MSG("InlinePwr:",current_block->laser.power));
2374
+              cutter.apply_power(current_block->laser.status.isPowered ? current_block->laser.power : 0);
2386
             #endif
2375
             #endif
2387
           }
2376
           }
2388
-        #endif
2389
-      #endif // LASER_POWER_INLINE
2377
+        }
2378
+      #endif // LASER_FEATURE
2390
 
2379
 
2391
       // If the endstop is already pressed, endstop interrupts won't invoke
2380
       // If the endstop is already pressed, endstop interrupts won't invoke
2392
       // endstop_triggered and the move will grind. So check here for a
2381
       // endstop_triggered and the move will grind. So check here for a
2416
       // Calculate the initial timer interval
2405
       // Calculate the initial timer interval
2417
       interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr);
2406
       interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr);
2418
     }
2407
     }
2419
-    #if ENABLED(LASER_POWER_INLINE_CONTINUOUS)
2420
-      else { // No new block found; so apply inline laser parameters
2421
-        // This should mean ending file with 'M5 I' will stop the laser; thus the inline flag isn't needed
2422
-        const power_status_t stat = planner.laser_inline.status;
2423
-        if (stat.isPlanned) {             // Planner controls the laser
2424
-          #if ENABLED(SPINDLE_LASER_USE_PWM)
2425
-            cutter.ocr_set_power(
2426
-              stat.isEnabled ? planner.laser_inline.power : 0 // ON with power or OFF
2427
-            );
2428
-          #else
2429
-            cutter.set_enabled(stat.isEnabled);
2430
-          #endif
2431
-        }
2432
-      }
2433
-    #endif
2434
   }
2408
   }
2435
 
2409
 
2436
   // Return the interval to wait
2410
   // Return the interval to wait

+ 0
- 19
Marlin/src/module/stepper.h Целия файл

444
     // Current stepper motor directions (+1 or -1)
444
     // Current stepper motor directions (+1 or -1)
445
     static xyze_int8_t count_direction;
445
     static xyze_int8_t count_direction;
446
 
446
 
447
-    #if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
448
-
449
-      typedef struct {
450
-        bool enabled;       // Trapezoid needed flag (i.e., laser on, planner in control)
451
-        uint8_t cur_power;  // Current laser power
452
-        bool cruise_set;    // Power set up for cruising?
453
-
454
-        #if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
455
-          uint16_t till_update;     // Countdown to the next update
456
-        #else
457
-          uint32_t last_step_count, // Step count from the last update
458
-                   acc_step_count;  // Bresenham counter for laser accel/decel
459
-        #endif
460
-      } stepper_laser_t;
461
-
462
-      static stepper_laser_t laser_trap;
463
-
464
-    #endif
465
-
466
   public:
447
   public:
467
     // Initialize stepper hardware
448
     // Initialize stepper hardware
468
     static void init();
449
     static void init();

+ 3
- 2
Marlin/src/module/temperature.cpp Целия файл

1904
   #if ENABLED(LASER_COOLANT_FLOW_METER)
1904
   #if ENABLED(LASER_COOLANT_FLOW_METER)
1905
     cooler.flowmeter_task(ms);
1905
     cooler.flowmeter_task(ms);
1906
     #if ENABLED(FLOWMETER_SAFETY)
1906
     #if ENABLED(FLOWMETER_SAFETY)
1907
-      if (cutter.enabled() && cooler.check_flow_too_low()) {
1907
+      if (cooler.check_flow_too_low()) {
1908
+        TERN_(HAS_DISPLAY, if (cutter.enabled()) ui.flow_fault());
1908
         cutter.disable();
1909
         cutter.disable();
1909
-        TERN_(HAS_DISPLAY, ui.flow_fault());
1910
+        cutter.cutter_mode = CUTTER_MODE_ERROR;   // Immediately kill stepper inline power output
1910
       }
1911
       }
1911
     #endif
1912
     #endif
1912
   #endif
1913
   #endif

+ 2
- 2
buildroot/tests/mega2560 Целия файл

189
         AXIS_RELATIVE_MODES '{ false, false, false }'
189
         AXIS_RELATIVE_MODES '{ false, false, false }'
190
 opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT MEATPACK_ON_SERIAL_PORT_1 \
190
 opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT MEATPACK_ON_SERIAL_PORT_1 \
191
            LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN
191
            LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN
192
-exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 12864 LCD | meatpack | SERIAL_PORT_2 " "$3"
192
+exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 12864 LCD | meatpack | Laser Safety Timeout | M3 Power Sync | Trap Power Smoothing | SERIAL_PORT_2 " "$3"
193
 
193
 
194
 #
194
 #
195
 # Test Laser features with 44780 LCD
195
 # Test Laser features with 44780 LCD
203
         AXIS_RELATIVE_MODES '{ false, false, false }'
203
         AXIS_RELATIVE_MODES '{ false, false, false }'
204
 opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER I2C_AMMETER \
204
 opt_enable REPRAP_DISCOUNT_SMART_CONTROLLER SDSUPPORT EEPROM_SETTINGS EEPROM_BOOT_SILENT EEPROM_AUTO_INIT PRINTCOUNTER I2C_AMMETER \
205
            LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN
205
            LASER_FEATURE LASER_SAFETY_TIMEOUT_MS LASER_COOLANT_FLOW_METER AIR_EVACUATION AIR_EVACUATION_PIN AIR_ASSIST AIR_ASSIST_PIN
206
-exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Flowmeter | 44780 LCD " "$3"
206
+exec_test $1 $2 "MEGA2560 RAMPS | Laser Feature | Air Evacuation | Air Assist | Cooler | Laser Safety Timeout | Flowmeter | 44780 LCD " "$3"
207
 
207
 
208
 #
208
 #
209
 # Test redundant temperature sensors + MAX TC
209
 # Test redundant temperature sensors + MAX TC

+ 137
- 0
docs/Cutter.md Целия файл

1
+### Introduction
2
+
3
+With Marlin version 2.0.9.x or higher, Laser improvements were introduced that enhance inline functionality. Previously the inline feature option was not operational without enabling and recompiling the source. Also with inline enabled the base features are not functional. With v2.0.9.x new functionality is added which allows the standard and inline modes to be G-Code selectable and also compatible with each other. Additionally an experimental dynamic mode is also available. Spindle operational features are available with defines and recompiling.
4
+
5
+### Architecture
6
+
7
+Laser selectable feature capability is defined through 4 global mode flags within gcode ,laser/spindle, planner and stepper routines. The default mode maintains the standard laser function. G-Codes are received, processed and parsed to determine what mode to set through M3, M4 and M5 commands. When the inline mode parameter set is detected, laser power processing will be driven through the planner and stepper routines. Handling of the initial power values and settings are performed by G-Code parsing and the laser/spindle routines.
8
+
9
+Inline power feeds from the block->inline_power variable into the planner's laser.power when in continuous power mode. Further power adjustment will be applied if the laser power trap feature is active otherwise laser.power is used as set in the stepper for the entire block. When laser power trap is active the power levels are step incremented during acceleration and step decremented during deceleration.
10
+
11
+Two additional power sets are fed in the planner by features laser power sync and laser fan power sync. Both of these power sets are done with planner sync block bit flags. With laser power sync, when the bit flag is matched the global block laser.power value is updated from laser/spindle standard M3 S-Value power sets. For laser fan sync, power values are updated into the planner block->fan_speed[i] variable from fan G-Code S-Value sets.
12
+
13
+With dynamic inline power mode, F-Value feedrate sets are processed with cutter.calc_dynamic_power() and fed into the planner laser.power value.
14
+
15
+Irrespective of what laser power value source is used, the final laser output pin is always updated using the laser/spindle code. Specifically the apply_power(value) call is used to set the laser or spindle output. This call permits safe power control in the event that a sensor fault occurs.
16
+
17
+Note: Spindle operation is not selectable with G-Codes at this time.
18
+
19
+The following flow charts depict the flow control logic for spindle and laser operations in the code base.
20
+
21
+#### Spindle Mode Logic:
22
+
23
+                ┌──────────┐  ┌───────────┐  ┌───────────┐
24
+                │M3 S-Value│  │Dir !same ?│  │Stepper    │
25
+                │Spindle   │  │stop & wait│  │processes  │
26
+             ┌──┤Clockwise ├──┤ & start   ├──┤moves      │
27
+    ┌─────┐  │  │          │  │spindle    │  │           │
28
+    │GCode│  │  └──────────┘  └───────────┘  └───────────┘
29
+    │Send ├──┤  ┌──────────┐  ┌───────────┐  ┌───────────┐
30
+    └─────┘  │  │M4 S-Value│  │Dir !same ?│  │Stepper    │
31
+             ├──┤Spindle   ├──┤stop & wait├──┤processes  │
32
+             │  │Counter   │  │& start    │  │moves      │
33
+             │  │Clockwise │  │spindle    │  │           │
34
+             │  └──────────┘  └───────────┘  └───────────┘
35
+             │  ┌──────────┐  ┌────────┐
36
+             │  │M5        │  │Wait for│
37
+             │  │Spindle   ├──┤move &  │
38
+             └──┤Stop      │  │disable │
39
+                └──────────┘  └────────┘
40
+                ┌──────────┐  ┌──────────┐
41
+    Sensors─────┤Fault     ├──┤Disable   │
42
+                └──────────┘  │power     │
43
+                              └──────────┘
44
+
45
+#### Laser Mode Logic:
46
+
47
+                ┌──────────┐  ┌─────────────┐  ┌───────────┐
48
+                │M3,M4,M5 I│  │Set power    │  │Stepper    │
49
+             ┌──┤Standard  ├──┤Immediately &├──┤processes  │
50
+             │  │Default   │  │wait for move│  │moves      │
51
+             │  │          │  │completion   │  │           │
52
+             │  └──────────┘  └─────────────┘  └───────────┘
53
+             │  ┌──────────┐  ┌───────────┐  ┌───────────┐  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌───────────┐
54
+    ┌─────┐  │  │M3 I      │  │G0,G1,G2,G4│  │Planner    │  │Planner     │  │Planner fan │  │Planner     │  │Stepper    │
55
+    │GCode│  │  │Continuous│  │M3 receive │  │sets block │  │sync power ?│  │sync power ?│  │trap power ?│  │uses block │
56
+    │Send ├──┼──┤Inline    ├──┤power from ├──┤power using├──┤process M3  ├──┤process fan ├──┤adjusts for ├──┤values to  │
57
+    └─────┘  │  │          │  │S-Value    │  │Gx S-Value │  │power inline│  │power inline│  │accel/decel │  │apply power│
58
+             │  └──────────┘  └───────────┘  └───────────┘  └────────────┘  └────────────┘  └────────────┘  └───────────┘
59
+             │  ┌──────────┐  ┌───────────┐  ┌────────────────┐  ┌───────────┐
60
+             │  │M4 I      │  │Gx F-Value │  │Planner         │  │Stepper    │
61
+             │  │Dynamic   │  │set power  │  │Calc & set block│  │uses block │
62
+             └──┤Inline    ├──┤or use     ├──┤block power     ├──┤values to  │
63
+                │          │  │default    │  │using F-Value   │  │apply power│
64
+                └──────────┘  └───────────┘  └────────────────┘  └───────────┘
65
+                ┌──────────┐  ┌──────────┐
66
+    Sensors─────┤Fault     ├──┤Disable   │
67
+                └──────────┘  │Power     │
68
+                              └──────────┘
69
+
70
+<!-- https://asciiflow.com/#/ -->
71
+
72
+### Continuous Inline Trap Power Calculations
73
+
74
+When LASER_FEATURE and LASER_POWER_TRAP are defined, planner calculations are performed and applied to the incoming laser power S-Value. The power will be factored and distributed across trapezoid acceleration and deceleration movements.
75
+
76
+When the laser.power > 0
77
+
78
+We set a minimum power if defined in SPEED_POWER_MIN it's fed into the planner block as laser_power_floor.
79
+
80
+A reduced entry laser power factor is based on the entry step rate to cruise step rate ratio for acceleration.
81
+
82
+    block entry laser power = laser power * ( entry step rate / cruise step rate )
83
+
84
+The initial power will be set to no less than the laser_power_floor or the inital power calculation.
85
+
86
+The reduced final power factor is based on the final step rate to cruise step rate ratio for deceleration.
87
+
88
+    block exit laser power = laser power * ( exit step rate / cruise step rate )
89
+
90
+Once the entry and exit power values are determined, the values are divided into step increments to be applied in the stepper.
91
+
92
+    trap step power incr_decr = ( cruize power - entry_exit ) / accel_decel_steps
93
+
94
+The trap steps are incremented or decremented during each accel or decel step until the block is complete.
95
+Step power is either cumulatively added or subtracted during trapeziod ramp progressions.
96
+
97
+#### Planner Code:
98
+
99
+   ```
100
+   if (block->laser.power > 0) {
101
+      NOLESS(block->laser.power, laser_power_floor);
102
+      block->laser.trap_ramp_active_pwr = (block->laser.power - laser_power_floor) * (initial_rate / float(block->nominal_rate)) + laser_power_floor;
103
+      block->laser.trap_ramp_entry_incr = (block->laser.power - block->laser.trap_ramp_active_pwr) / accelerate_steps;
104
+      float laser_pwr = block->laser.power * (final_rate / float(block->nominal_rate));
105
+      NOLESS(laser_pwr, laser_power_floor);
106
+      block->laser.trap_ramp_exit_decr = (block->laser.power - laser_pwr) / decelerate_steps;
107
+   ```
108
+
109
+#### Stepper Code:
110
+
111
+   ```
112
+   if (current_block->laser.trap_ramp_entry_incr > 0) {
113
+      cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
114
+      current_block->laser.trap_ramp_active_pwr += current_block->laser.trap_ramp_entry_incr;
115
+   ```
116
+
117
+   ```
118
+   if (current_block->laser.trap_ramp_exit_decr > 0) {
119
+      current_block->laser.trap_ramp_active_pwr -= current_block->laser.trap_ramp_exit_decr;
120
+      cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
121
+   ```
122
+
123
+### Dynamic Inline Calculations
124
+
125
+Dynamic mode will calculate laser power based on the F-Value feedrate. The method uses bit shifting to set a power level from 0 to 255. It's simple and fast and we can use a scaler to shift the laser power output to center on a given power level.
126
+
127
+#### Spindle/Laser Code:
128
+
129
+```
130
+    // Dynamic mode rate calculation
131
+    static inline uint8_t calc_dynamic_power() {
132
+      if (feedrate_mm_m > 65535) return 255;         // Too fast, go always on
133
+      uint16_t rate = uint16_t(feedrate_mm_m);       // 16 bits from the G-code parser float input
134
+      rate >>= 8;                                    // Take the G-code input e.g. F40000 and shift off the lower bits to get an OCR value from 1-255
135
+      return uint8_t(rate);
136
+    }
137
+```

Loading…
Отказ
Запис