⚡️ Fix and improve Inline Laser Power (#22690)
This commit is contained in:
committed by
Scott Lahteine
parent
5b6c46db29
commit
d965303a7a
@ -3668,8 +3668,11 @@
|
||||
#endif
|
||||
|
||||
// Define the minimum and maximum test pulse time values for a laser test fire function
|
||||
#define LASER_TEST_PULSE_MIN 1 // Used with Laser Control Menu
|
||||
#define LASER_TEST_PULSE_MAX 999 // Caution: Menu may not show more than 3 characters
|
||||
#define LASER_TEST_PULSE_MIN 1 // (ms) Used with Laser Control Menu
|
||||
#define LASER_TEST_PULSE_MAX 999 // (ms) Caution: Menu may not show more than 3 characters
|
||||
|
||||
#define SPINDLE_LASER_POWERUP_DELAY 50 // (ms) Delay to allow the spindle/laser to come up to speed/power
|
||||
#define SPINDLE_LASER_POWERDOWN_DELAY 50 // (ms) Delay to allow the spindle to stop
|
||||
|
||||
/**
|
||||
* Laser Safety Timeout
|
||||
@ -3682,79 +3685,38 @@
|
||||
#define LASER_SAFETY_TIMEOUT_MS 1000 // (ms)
|
||||
|
||||
/**
|
||||
* Enable inline laser power to be handled in the planner / stepper routines.
|
||||
* Inline power is specified by the I (inline) flag in an M3 command (e.g., M3 S20 I)
|
||||
* or by the 'S' parameter in G0/G1/G2/G3 moves (see LASER_MOVE_POWER).
|
||||
* Any M3 or G1/2/3/5 command with the 'I' parameter enables continuous inline power mode.
|
||||
*
|
||||
* This allows the laser to keep in perfect sync with the planner and removes
|
||||
* the powerup/down delay since lasers require negligible time.
|
||||
* e.g., 'M3 I' enables continuous inline power which is processed by the planner.
|
||||
* Power is stored in move blocks and applied when blocks are processed by the Stepper ISR.
|
||||
*
|
||||
* 'M4 I' sets dynamic mode which uses the current feedrate to calculate a laser power OCR value.
|
||||
*
|
||||
* Any move in dynamic mode will use the current feedrate to calculate the laser power.
|
||||
* Feed rates are set by the F parameter of a move command e.g. G1 X0 Y10 F6000
|
||||
* Laser power would be calculated by bit shifting off 8 LSB's. In binary this is div 256.
|
||||
* The calculation gives us ocr values from 0 to 255, values over F65535 will be set as 255 .
|
||||
* More refined power control such as compesation for accell/decell will be addressed in future releases.
|
||||
*
|
||||
* M5 I clears inline mode and set power to 0, M5 sets the power output to 0 but leaves inline mode on.
|
||||
*/
|
||||
//#define LASER_POWER_INLINE
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
/**
|
||||
* Scale the laser's power in proportion to the movement rate.
|
||||
*
|
||||
* - Sets the entry power proportional to the entry speed over the nominal speed.
|
||||
* - Ramps the power up every N steps to approximate the speed trapezoid.
|
||||
* - Due to the limited power resolution this is only approximate.
|
||||
*/
|
||||
#define LASER_POWER_INLINE_TRAPEZOID
|
||||
/**
|
||||
* Enable M3 commands for laser mode inline power planner syncing.
|
||||
* This feature enables any M3 S-value to be injected into the block buffers while in
|
||||
* CUTTER_MODE_CONTINUOUS. The option allows M3 laser power to be commited without waiting
|
||||
* for a planner syncronization
|
||||
*/
|
||||
//#define LASER_POWER_SYNC
|
||||
|
||||
/**
|
||||
* Continuously calculate the current power (nominal_power * current_rate / nominal_rate).
|
||||
* Required for accurate power with non-trapezoidal acceleration (e.g., S_CURVE_ACCELERATION).
|
||||
* This is a costly calculation so this option is discouraged on 8-bit AVR boards.
|
||||
*
|
||||
* LASER_POWER_INLINE_TRAPEZOID_CONT_PER defines how many step cycles there are between power updates. If your
|
||||
* board isn't able to generate steps fast enough (and you are using LASER_POWER_INLINE_TRAPEZOID_CONT), increase this.
|
||||
* Note that when this is zero it means it occurs every cycle; 1 means a delay wait one cycle then run, etc.
|
||||
*/
|
||||
//#define LASER_POWER_INLINE_TRAPEZOID_CONT
|
||||
|
||||
/**
|
||||
* Stepper iterations between power updates. Increase this value if the board
|
||||
* can't keep up with the processing demands of LASER_POWER_INLINE_TRAPEZOID_CONT.
|
||||
* Disable (or set to 0) to recalculate power on every stepper iteration.
|
||||
*/
|
||||
//#define LASER_POWER_INLINE_TRAPEZOID_CONT_PER 10
|
||||
|
||||
/**
|
||||
* Include laser power in G0/G1/G2/G3/G5 commands with the 'S' parameter
|
||||
*/
|
||||
//#define LASER_MOVE_POWER
|
||||
|
||||
#if ENABLED(LASER_MOVE_POWER)
|
||||
// Turn off the laser on G0 moves with no power parameter.
|
||||
// If a power parameter is provided, use that instead.
|
||||
//#define LASER_MOVE_G0_OFF
|
||||
|
||||
// Turn off the laser on G28 homing.
|
||||
//#define LASER_MOVE_G28_OFF
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Inline flag inverted
|
||||
*
|
||||
* WARNING: M5 will NOT turn off the laser unless another move
|
||||
* is done (so G-code files must end with 'M5 I').
|
||||
*/
|
||||
//#define LASER_POWER_INLINE_INVERT
|
||||
|
||||
/**
|
||||
* Continuously apply inline power. ('M3 S3' == 'G1 S3' == 'M3 S3 I')
|
||||
*
|
||||
* The laser might do some weird things, so only enable this
|
||||
* feature if you understand the implications.
|
||||
*/
|
||||
//#define LASER_POWER_INLINE_CONTINUOUS
|
||||
|
||||
#else
|
||||
|
||||
#define SPINDLE_LASER_POWERUP_DELAY 50 // (ms) Delay to allow the spindle/laser to come up to speed/power
|
||||
#define SPINDLE_LASER_POWERDOWN_DELAY 50 // (ms) Delay to allow the spindle to stop
|
||||
|
||||
#endif
|
||||
/**
|
||||
* Scale the laser's power in proportion to the movement rate.
|
||||
*
|
||||
* - Sets the entry power proportional to the entry speed over the nominal speed.
|
||||
* - Ramps the power up every N steps to approximate the speed trapezoid.
|
||||
* - Due to the limited power resolution this is only approximate.
|
||||
*/
|
||||
//#define LASER_POWER_TRAP
|
||||
|
||||
//
|
||||
// Laser I2C Ammeter (High precision INA226 low/high side module)
|
||||
|
@ -39,18 +39,26 @@
|
||||
#endif
|
||||
|
||||
SpindleLaser cutter;
|
||||
uint8_t SpindleLaser::power,
|
||||
bool SpindleLaser::enable_state; // Virtual enable state, controls enable pin if present and or apply power if > 0
|
||||
uint8_t SpindleLaser::power, // Actual power output 0-255 ocr or "0 = off" > 0 = "on"
|
||||
SpindleLaser::last_power_applied; // = 0 // Basic power state tracking
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
cutter_test_pulse_t SpindleLaser::testPulse = 50; // Test fire Pulse time ms value.
|
||||
#endif
|
||||
bool SpindleLaser::isReady; // Ready to apply power setting from the UI to OCR
|
||||
cutter_power_t SpindleLaser::menuPower, // Power set via LCD menu in PWM, PERCENT, or RPM
|
||||
SpindleLaser::unitPower; // LCD status power in PWM, PERCENT, or RPM
|
||||
|
||||
#if ENABLED(MARLIN_DEV_MODE)
|
||||
cutter_frequency_t SpindleLaser::frequency; // PWM frequency setting; range: 2K - 50K
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
cutter_test_pulse_t SpindleLaser::testPulse = 50; // (ms) Test fire pulse default duration
|
||||
uint8_t SpindleLaser::last_block_power; // = 0 // Track power changes for dynamic inline power
|
||||
feedRate_t SpindleLaser::feedrate_mm_m = 1500,
|
||||
SpindleLaser::last_feedrate_mm_m; // = 0 // (mm/min) Track feedrate changes for dynamic power
|
||||
#endif
|
||||
|
||||
bool SpindleLaser::isReadyForUI = false; // Ready to apply power setting from the UI to OCR
|
||||
CutterMode SpindleLaser::cutter_mode = CUTTER_MODE_STANDARD; // Default is standard mode
|
||||
|
||||
constexpr cutter_cpower_t SpindleLaser::power_floor;
|
||||
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.
|
||||
SpindleLaser::unitPower = 0; // Unit power is in PWM, PERCENT, or RPM based on CUTTER_POWER_UNIT.
|
||||
|
||||
cutter_frequency_t SpindleLaser::frequency; // PWM frequency setting; range: 2K - 50K
|
||||
|
||||
#define SPINDLE_LASER_PWM_OFF TERN(SPINDLE_LASER_PWM_INVERT, 255, 0)
|
||||
|
||||
/**
|
||||
@ -65,14 +73,14 @@ void SpindleLaser::init() {
|
||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||
OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR); // Init rotation to clockwise (M3)
|
||||
#endif
|
||||
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
|
||||
frequency = SPINDLE_LASER_FREQUENCY;
|
||||
hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
|
||||
#endif
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
SET_PWM(SPINDLE_LASER_PWM_PIN);
|
||||
hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Set to lowest speed
|
||||
#endif
|
||||
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
|
||||
hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
|
||||
TERN_(MARLIN_DEV_MODE, frequency = SPINDLE_LASER_FREQUENCY);
|
||||
#endif
|
||||
#if ENABLED(AIR_EVACUATION)
|
||||
OUT_WRITE(AIR_EVACUATION_PIN, !AIR_EVACUATION_ACTIVE); // Init Vacuum/Blower OFF
|
||||
#endif
|
||||
@ -90,7 +98,7 @@ void SpindleLaser::init() {
|
||||
*/
|
||||
void SpindleLaser::_set_ocr(const uint8_t ocr) {
|
||||
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
|
||||
hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), TERN(MARLIN_DEV_MODE, frequency, SPINDLE_LASER_FREQUENCY));
|
||||
hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency);
|
||||
#endif
|
||||
hal.set_pwm_duty(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
|
||||
}
|
||||
@ -107,35 +115,41 @@ void SpindleLaser::init() {
|
||||
#endif // SPINDLE_LASER_USE_PWM
|
||||
|
||||
/**
|
||||
* Apply power for laser/spindle
|
||||
* Apply power for Laser or Spindle
|
||||
*
|
||||
* Apply cutter power value for PWM, Servo, and on/off pin.
|
||||
*
|
||||
* @param opwr Power value. Range 0 to MAX. When 0 disable spindle/laser.
|
||||
* @param opwr Power value. Range 0 to MAX.
|
||||
*/
|
||||
void SpindleLaser::apply_power(const uint8_t opwr) {
|
||||
if (opwr == last_power_applied) return;
|
||||
last_power_applied = opwr;
|
||||
power = opwr;
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
if (cutter.unitPower == 0 && CUTTER_UNIT_IS(RPM)) {
|
||||
ocr_off();
|
||||
isReady = false;
|
||||
}
|
||||
else if (ENABLED(CUTTER_POWER_RELATIVE) || enabled()) {
|
||||
set_ocr(power);
|
||||
isReady = true;
|
||||
}
|
||||
else {
|
||||
ocr_off();
|
||||
isReady = false;
|
||||
}
|
||||
#elif ENABLED(SPINDLE_SERVO)
|
||||
servo[SPINDLE_SERVO_NR].move(power);
|
||||
#else
|
||||
WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
|
||||
isReady = true;
|
||||
#endif
|
||||
if (enabled() || opwr == 0) { // 0 check allows us to disable where no ENA pin exists
|
||||
// Test and set the last power used to improve performance
|
||||
if (opwr == last_power_applied) return;
|
||||
last_power_applied = opwr;
|
||||
// Handle PWM driven or just simple on/off
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
if (CUTTER_UNIT_IS(RPM) && unitPower == 0)
|
||||
ocr_off();
|
||||
else if (ENABLED(CUTTER_POWER_RELATIVE) || enabled() || opwr == 0) {
|
||||
set_ocr(opwr);
|
||||
isReadyForUI = true;
|
||||
}
|
||||
else
|
||||
ocr_off();
|
||||
#elif ENABLED(SPINDLE_SERVO)
|
||||
MOVE_SERVO(SPINDLE_SERVO_NR, power);
|
||||
#else
|
||||
WRITE(SPINDLE_LASER_ENA_PIN, enabled() ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
|
||||
isReadyForUI = true;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
#if PIN_EXISTS(SPINDLE_LASER_ENA)
|
||||
WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_STATE);
|
||||
#endif
|
||||
isReadyForUI = false; // Only used for UI display updates.
|
||||
TERN_(SPINDLE_LASER_USE_PWM, ocr_off());
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||
|
@ -34,85 +34,98 @@
|
||||
#include "../libs/buzzer.h"
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
#include "../module/planner.h"
|
||||
#endif
|
||||
// Inline laser power
|
||||
#include "../module/planner.h"
|
||||
|
||||
#define PCT_TO_PWM(X) ((X) * 255 / 100)
|
||||
#define PCT_TO_SERVO(X) ((X) * 180 / 100)
|
||||
|
||||
|
||||
// Laser/Cutter operation mode
|
||||
enum CutterMode : int8_t {
|
||||
CUTTER_MODE_ERROR = -1,
|
||||
CUTTER_MODE_STANDARD, // M3 power is applied directly and waits for planner moves to sync.
|
||||
CUTTER_MODE_CONTINUOUS, // M3 or G1/2/3 move power is controlled within planner blocks, set with 'M3 I', cleared with 'M5 I'.
|
||||
CUTTER_MODE_DYNAMIC // M4 laser power is proportional to the feed rate, set with 'M4 I', cleared with 'M5 I'.
|
||||
};
|
||||
|
||||
class SpindleLaser {
|
||||
public:
|
||||
static const inline uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); }
|
||||
static CutterMode cutter_mode;
|
||||
|
||||
static constexpr uint8_t pct_to_ocr(const_float_t pct) { return uint8_t(PCT_TO_PWM(pct)); }
|
||||
|
||||
// cpower = configured values (e.g., SPEED_POWER_MAX)
|
||||
|
||||
// Convert configured power range to a percentage
|
||||
static const inline uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) {
|
||||
constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0),
|
||||
power_range = SPEED_POWER_MAX - power_floor;
|
||||
return cpwr ? round(100.0f * (cpwr - power_floor) / power_range) : 0;
|
||||
static constexpr cutter_cpower_t power_floor = TERN(CUTTER_POWER_RELATIVE, SPEED_POWER_MIN, 0);
|
||||
static constexpr uint8_t cpwr_to_pct(const cutter_cpower_t cpwr) {
|
||||
return cpwr ? round(100.0f * (cpwr - power_floor) / (SPEED_POWER_MAX - power_floor)) : 0;
|
||||
}
|
||||
|
||||
// Convert a cpower (e.g., SPEED_POWER_STARTUP) to unit power (upwr, upower),
|
||||
// which can be PWM, Percent, Servo angle, or RPM (rel/abs).
|
||||
static const inline cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power
|
||||
const cutter_power_t upwr = (
|
||||
// Convert config defines from RPM to %, angle or PWM when in Spindle mode
|
||||
// and convert from PERCENT to PWM when in Laser mode
|
||||
static constexpr cutter_power_t cpwr_to_upwr(const cutter_cpower_t cpwr) { // STARTUP power to Unit power
|
||||
return (
|
||||
#if ENABLED(SPINDLE_FEATURE)
|
||||
// Spindle configured values are in RPM
|
||||
// Spindle configured define values are in RPM
|
||||
#if CUTTER_UNIT_IS(RPM)
|
||||
cpwr // to RPM
|
||||
#elif CUTTER_UNIT_IS(PERCENT) // to PCT
|
||||
cpwr_to_pct(cpwr)
|
||||
#elif CUTTER_UNIT_IS(SERVO) // to SERVO angle
|
||||
PCT_TO_SERVO(cpwr_to_pct(cpwr))
|
||||
#else // to PWM
|
||||
PCT_TO_PWM(cpwr_to_pct(cpwr))
|
||||
cpwr // to same
|
||||
#elif CUTTER_UNIT_IS(PERCENT)
|
||||
cpwr_to_pct(cpwr) // to Percent
|
||||
#elif CUTTER_UNIT_IS(SERVO)
|
||||
PCT_TO_SERVO(cpwr_to_pct(cpwr)) // to SERVO angle
|
||||
#else
|
||||
PCT_TO_PWM(cpwr_to_pct(cpwr)) // to PWM
|
||||
#endif
|
||||
#else
|
||||
// Laser configured values are in PCT
|
||||
// Laser configured define values are in Percent
|
||||
#if CUTTER_UNIT_IS(PWM255)
|
||||
PCT_TO_PWM(cpwr)
|
||||
PCT_TO_PWM(cpwr) // to PWM
|
||||
#else
|
||||
cpwr // to RPM/PCT
|
||||
cpwr // to same
|
||||
#endif
|
||||
#endif
|
||||
);
|
||||
return upwr;
|
||||
}
|
||||
|
||||
static const cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); }
|
||||
static const cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); }
|
||||
static constexpr cutter_power_t mpower_min() { return cpwr_to_upwr(SPEED_POWER_MIN); }
|
||||
static constexpr cutter_power_t mpower_max() { return cpwr_to_upwr(SPEED_POWER_MAX); }
|
||||
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
static cutter_test_pulse_t testPulse; // Test fire Pulse ms value
|
||||
static cutter_test_pulse_t testPulse; // (ms) Test fire pulse duration
|
||||
static uint8_t last_block_power; // Track power changes for dynamic power
|
||||
|
||||
static feedRate_t feedrate_mm_m, last_feedrate_mm_m; // (mm/min) Track feedrate changes for dynamic power
|
||||
static bool laser_feedrate_changed() {
|
||||
const bool changed = last_feedrate_mm_m != feedrate_mm_m;
|
||||
if (changed) last_feedrate_mm_m = feedrate_mm_m;
|
||||
return changed;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool isReady; // Ready to apply power setting from the UI to OCR
|
||||
static bool isReadyForUI; // Ready to apply power setting from the UI to OCR
|
||||
static bool enable_state;
|
||||
static uint8_t power,
|
||||
last_power_applied; // Basic power state tracking
|
||||
|
||||
#if ENABLED(MARLIN_DEV_MODE)
|
||||
static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K
|
||||
#endif
|
||||
static cutter_frequency_t frequency; // Set PWM frequency; range: 2K-50K
|
||||
|
||||
static cutter_power_t menuPower, // Power as set via LCD menu in PWM, Percentage or RPM
|
||||
unitPower; // Power as displayed status in PWM, Percentage or RPM
|
||||
|
||||
static void init();
|
||||
|
||||
#if ENABLED(MARLIN_DEV_MODE)
|
||||
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
|
||||
static void refresh_frequency() { hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
|
||||
#endif
|
||||
|
||||
// Modifying this function should update everywhere
|
||||
static bool enabled(const cutter_power_t opwr) { return opwr > 0; }
|
||||
static bool enabled() { return enabled(power); }
|
||||
static bool enabled() { return enable_state; }
|
||||
|
||||
static void apply_power(const uint8_t inpow);
|
||||
|
||||
FORCE_INLINE static void refresh() { apply_power(power); }
|
||||
FORCE_INLINE static void set_power(const uint8_t upwr) { power = upwr; refresh(); }
|
||||
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
|
||||
@ -123,7 +136,6 @@ public:
|
||||
public:
|
||||
|
||||
static void set_ocr(const uint8_t ocr);
|
||||
static void ocr_set_power(const uint8_t ocr) { power = ocr; set_ocr(ocr); }
|
||||
static void ocr_off();
|
||||
|
||||
/**
|
||||
@ -141,78 +153,76 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct power to configured range
|
||||
*/
|
||||
static cutter_power_t power_to_range(const cutter_power_t pwr) {
|
||||
return power_to_range(pwr, _CUTTER_POWER(CUTTER_POWER_UNIT));
|
||||
}
|
||||
|
||||
static cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit) {
|
||||
static constexpr float
|
||||
min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)),
|
||||
max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX);
|
||||
if (pwr <= 0) return 0;
|
||||
cutter_power_t upwr;
|
||||
switch (pwrUnit) {
|
||||
case _CUTTER_POWER_PWM255:
|
||||
upwr = cutter_power_t(
|
||||
(pwr < pct_to_ocr(min_pct)) ? pct_to_ocr(min_pct) // Use minimum if set below
|
||||
: (pwr > pct_to_ocr(max_pct)) ? pct_to_ocr(max_pct) // Use maximum if set above
|
||||
: pwr
|
||||
);
|
||||
break;
|
||||
case _CUTTER_POWER_PERCENT:
|
||||
upwr = cutter_power_t(
|
||||
(pwr < min_pct) ? min_pct // Use minimum if set below
|
||||
: (pwr > max_pct) ? max_pct // Use maximum if set above
|
||||
: pwr // PCT
|
||||
);
|
||||
break;
|
||||
case _CUTTER_POWER_RPM:
|
||||
upwr = cutter_power_t(
|
||||
(pwr < SPEED_POWER_MIN) ? SPEED_POWER_MIN // Use minimum if set below
|
||||
: (pwr > SPEED_POWER_MAX) ? SPEED_POWER_MAX // Use maximum if set above
|
||||
: pwr // Calculate OCR value
|
||||
);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return upwr;
|
||||
}
|
||||
|
||||
#endif // SPINDLE_LASER_USE_PWM
|
||||
|
||||
/**
|
||||
* Enable/Disable spindle/laser
|
||||
* @param enable true = enable; false = disable
|
||||
* Correct power to configured range
|
||||
*/
|
||||
static void set_enabled(const bool enable) {
|
||||
uint8_t value = 0;
|
||||
if (enable) {
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
if (power)
|
||||
value = power;
|
||||
else if (unitPower)
|
||||
value = upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP));
|
||||
#else
|
||||
value = 255;
|
||||
#endif
|
||||
static cutter_power_t power_to_range(const cutter_power_t pwr, const uint8_t pwrUnit=_CUTTER_POWER(CUTTER_POWER_UNIT)) {
|
||||
static constexpr float
|
||||
min_pct = TERN(CUTTER_POWER_RELATIVE, 0, TERN(SPINDLE_FEATURE, round(100.0f * (SPEED_POWER_MIN) / (SPEED_POWER_MAX)), SPEED_POWER_MIN)),
|
||||
max_pct = TERN(SPINDLE_FEATURE, 100, SPEED_POWER_MAX);
|
||||
if (pwr <= 0) return 0;
|
||||
cutter_power_t upwr;
|
||||
switch (pwrUnit) {
|
||||
case _CUTTER_POWER_PWM255: { // PWM
|
||||
const uint8_t pmin = pct_to_ocr(min_pct), pmax = pct_to_ocr(max_pct);
|
||||
upwr = cutter_power_t(constrain(pwr, pmin, pmax));
|
||||
} break;
|
||||
case _CUTTER_POWER_PERCENT: // Percent
|
||||
upwr = cutter_power_t(constrain(pwr, min_pct, max_pct));
|
||||
break;
|
||||
case _CUTTER_POWER_RPM: // Calculate OCR value
|
||||
upwr = cutter_power_t(constrain(pwr, SPEED_POWER_MIN, SPEED_POWER_MAX));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
set_power(value);
|
||||
return upwr;
|
||||
}
|
||||
|
||||
static void disable() { isReady = false; set_enabled(false); }
|
||||
|
||||
/**
|
||||
* Wait for spindle to spin up or spin down
|
||||
* Enable Laser or Spindle output.
|
||||
* It's important to prevent changing the power output value during inline cutter operation.
|
||||
* Inline power is adjusted in the planner to support LASER_TRAP_POWER and CUTTER_MODE_DYNAMIC mode.
|
||||
*
|
||||
* @param on true = state to on; false = state to off.
|
||||
* This method accepts one of the following control states:
|
||||
*
|
||||
* - For CUTTER_MODE_STANDARD the cutter power is either full on/off or ocr-based and it will apply
|
||||
* SPEED_POWER_STARTUP if no value is assigned.
|
||||
*
|
||||
* - For CUTTER_MODE_CONTINUOUS inline and power remains where last set and the cutter output enable flag is set.
|
||||
*
|
||||
* - CUTTER_MODE_DYNAMIC is also inline-based and it just sets the enable output flag.
|
||||
*
|
||||
* - For CUTTER_MODE_ERROR set the output enable_state flag directly and set power to 0 for any mode.
|
||||
* This mode allows a global power shutdown action to occur.
|
||||
*/
|
||||
static void power_delay(const bool on) {
|
||||
#if DISABLED(LASER_POWER_INLINE)
|
||||
safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
|
||||
static void set_enabled(const bool enable) {
|
||||
switch (cutter_mode) {
|
||||
case CUTTER_MODE_STANDARD:
|
||||
apply_power(enable ? TERN(SPINDLE_LASER_USE_PWM, (power ?: (unitPower ? upower_to_ocr(cpwr_to_upwr(SPEED_POWER_STARTUP)) : 0)), 255) : 0);
|
||||
break;
|
||||
case CUTTER_MODE_CONTINUOUS:
|
||||
TERN_(LASER_FEATURE, set_inline_enabled(enable));
|
||||
break;
|
||||
case CUTTER_MODE_DYNAMIC:
|
||||
TERN_(LASER_FEATURE, set_inline_enabled(enable));
|
||||
break;
|
||||
case CUTTER_MODE_ERROR: // Error mode, no enable and kill power.
|
||||
enable_state = false;
|
||||
apply_power(0);
|
||||
}
|
||||
#if SPINDLE_LASER_ENA_PIN
|
||||
WRITE(SPINDLE_LASER_ENA_PIN, enable ? SPINDLE_LASER_ACTIVE_STATE : !SPINDLE_LASER_ACTIVE_STATE);
|
||||
#endif
|
||||
enable_state = enable;
|
||||
}
|
||||
|
||||
static void disable() { isReadyForUI = false; set_enabled(false); }
|
||||
|
||||
// Wait for spindle/laser to startup or shutdown
|
||||
static void power_delay(const bool on) {
|
||||
safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
|
||||
}
|
||||
|
||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||
@ -224,47 +234,60 @@ public:
|
||||
#endif
|
||||
|
||||
#if ENABLED(AIR_EVACUATION)
|
||||
static void air_evac_enable(); // Turn On Cutter Vacuum or Laser Blower motor
|
||||
static void air_evac_disable(); // Turn Off Cutter Vacuum or Laser Blower motor
|
||||
static void air_evac_toggle(); // Toggle Cutter Vacuum or Laser Blower motor
|
||||
static bool air_evac_state() { // Get current state
|
||||
static void air_evac_enable(); // Turn On Cutter Vacuum or Laser Blower motor
|
||||
static void air_evac_disable(); // Turn Off Cutter Vacuum or Laser Blower motor
|
||||
static void air_evac_toggle(); // Toggle Cutter Vacuum or Laser Blower motor
|
||||
static bool air_evac_state() { // Get current state
|
||||
return (READ(AIR_EVACUATION_PIN) == AIR_EVACUATION_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLED(AIR_ASSIST)
|
||||
static void air_assist_enable(); // Turn on air assist
|
||||
static void air_assist_disable(); // Turn off air assist
|
||||
static void air_assist_toggle(); // Toggle air assist
|
||||
static bool air_assist_state() { // Get current state
|
||||
static void air_assist_enable(); // Turn on air assist
|
||||
static void air_assist_disable(); // Turn off air assist
|
||||
static void air_assist_toggle(); // Toggle air assist
|
||||
static bool air_assist_state() { // Get current state
|
||||
return (READ(AIR_ASSIST_PIN) == AIR_ASSIST_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAS_MARLINUI_MENU
|
||||
static void enable_with_dir(const bool reverse) {
|
||||
isReady = true;
|
||||
const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255);
|
||||
if (menuPower)
|
||||
power = ocr;
|
||||
else
|
||||
menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
|
||||
unitPower = menuPower;
|
||||
set_reverse(reverse);
|
||||
set_enabled(true);
|
||||
}
|
||||
FORCE_INLINE static void enable_forward() { enable_with_dir(false); }
|
||||
FORCE_INLINE static void enable_reverse() { enable_with_dir(true); }
|
||||
FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); }
|
||||
|
||||
#if ENABLED(SPINDLE_FEATURE)
|
||||
static void enable_with_dir(const bool reverse) {
|
||||
isReadyForUI = true;
|
||||
const uint8_t ocr = TERN(SPINDLE_LASER_USE_PWM, upower_to_ocr(menuPower), 255);
|
||||
if (menuPower)
|
||||
power = ocr;
|
||||
else
|
||||
menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
|
||||
unitPower = menuPower;
|
||||
set_reverse(reverse);
|
||||
set_enabled(true);
|
||||
}
|
||||
FORCE_INLINE static void enable_forward() { enable_with_dir(false); }
|
||||
FORCE_INLINE static void enable_reverse() { enable_with_dir(true); }
|
||||
FORCE_INLINE static void enable_same_dir() { enable_with_dir(is_reverse()); }
|
||||
#endif // SPINDLE_FEATURE
|
||||
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
static void update_from_mpower() {
|
||||
if (isReady) power = upower_to_ocr(menuPower);
|
||||
if (isReadyForUI) power = upower_to_ocr(menuPower);
|
||||
unitPower = menuPower;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
// Toggle the laser on/off with menuPower. Apply SPEED_POWER_STARTUP if it was 0 on entry.
|
||||
static void laser_menu_toggle(const bool state) {
|
||||
set_enabled(state);
|
||||
if (state) {
|
||||
if (!menuPower) menuPower = cpwr_to_upwr(SPEED_POWER_STARTUP);
|
||||
power = upower_to_ocr(menuPower);
|
||||
apply_power(power);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fire the laser using the testPulse ms duration
|
||||
* Also fires with any PWM power that was previous set
|
||||
@ -272,74 +295,36 @@ public:
|
||||
*/
|
||||
static void test_fire_pulse() {
|
||||
TERN_(HAS_BEEPER, buzzer.tone(30, 3000));
|
||||
enable_forward(); // Turn Laser on (Spindle speak but same funct)
|
||||
cutter_mode = CUTTER_MODE_STANDARD;// Menu needs standard mode.
|
||||
laser_menu_toggle(true); // Laser On
|
||||
delay(testPulse); // Delay for time set by user in pulse ms menu screen.
|
||||
disable(); // Turn laser off
|
||||
laser_menu_toggle(false); // Laser Off
|
||||
}
|
||||
#endif
|
||||
#endif // LASER_FEATURE
|
||||
|
||||
#endif // HAS_MARLINUI_MENU
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
/**
|
||||
* Inline power adds extra fields to the planner block
|
||||
* to handle laser power and scale to movement speed.
|
||||
*/
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
|
||||
// Force disengage planner power control
|
||||
static void inline_disable() {
|
||||
isReady = false;
|
||||
unitPower = 0;
|
||||
planner.laser_inline.status.isPlanned = false;
|
||||
planner.laser_inline.status.isEnabled = false;
|
||||
planner.laser_inline.power = 0;
|
||||
// Dynamic mode rate calculation
|
||||
static uint8_t calc_dynamic_power() {
|
||||
if (feedrate_mm_m > 65535) return 255; // Too fast, go always on
|
||||
uint16_t rate = uint16_t(feedrate_mm_m); // 16 bits from the G-code parser float input
|
||||
rate >>= 8; // Take the G-code input e.g. F40000 and shift off the lower bits to get an OCR value from 1-255
|
||||
return uint8_t(rate);
|
||||
}
|
||||
|
||||
// Inline modes of all other functions; all enable planner inline power control
|
||||
static void set_inline_enabled(const bool enable) {
|
||||
if (enable)
|
||||
inline_power(255);
|
||||
else {
|
||||
isReady = false;
|
||||
unitPower = menuPower = 0;
|
||||
planner.laser_inline.status.isPlanned = false;
|
||||
TERN(SPINDLE_LASER_USE_PWM, inline_ocr_power, inline_power)(0);
|
||||
}
|
||||
}
|
||||
static void set_inline_enabled(const bool enable) { planner.laser_inline.status.isEnabled = enable;}
|
||||
|
||||
// Set the power for subsequent movement blocks
|
||||
static void inline_power(const cutter_power_t upwr) {
|
||||
unitPower = menuPower = upwr;
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
#if ENABLED(SPEED_POWER_RELATIVE) && !CUTTER_UNIT_IS(RPM) // relative mode does not turn laser off at 0, except for RPM
|
||||
planner.laser_inline.status.isEnabled = true;
|
||||
planner.laser_inline.power = upower_to_ocr(upwr);
|
||||
isReady = true;
|
||||
#else
|
||||
inline_ocr_power(upower_to_ocr(upwr));
|
||||
#endif
|
||||
#else
|
||||
planner.laser_inline.status.isEnabled = enabled(upwr);
|
||||
planner.laser_inline.power = upwr;
|
||||
isReady = enabled(upwr);
|
||||
#endif
|
||||
static void inline_power(const cutter_power_t cpwr) {
|
||||
TERN(SPINDLE_LASER_USE_PWM, power = planner.laser_inline.power = cpwr, planner.laser_inline.power = cpwr > 0 ? 255 : 0);
|
||||
}
|
||||
|
||||
static void inline_direction(const bool) { /* never */ }
|
||||
#endif // LASER_FEATURE
|
||||
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
static void inline_ocr_power(const uint8_t ocrpwr) {
|
||||
isReady = ocrpwr > 0;
|
||||
planner.laser_inline.status.isEnabled = ocrpwr > 0;
|
||||
planner.laser_inline.power = ocrpwr;
|
||||
}
|
||||
#endif
|
||||
#endif // LASER_POWER_INLINE
|
||||
|
||||
static void kill() {
|
||||
TERN_(LASER_POWER_INLINE, inline_disable());
|
||||
disable();
|
||||
}
|
||||
static void kill() { disable(); }
|
||||
};
|
||||
|
||||
extern SpindleLaser cutter;
|
||||
|
@ -74,12 +74,10 @@ typedef IF<(SPEED_POWER_MAX > 255), uint16_t, uint8_t>::type cutter_cpower_t;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
typedef uint16_t cutter_frequency_t;
|
||||
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
typedef uint16_t cutter_test_pulse_t;
|
||||
#define CUTTER_MENU_PULSE_TYPE uint16_3
|
||||
#endif
|
||||
|
||||
#if ENABLED(MARLIN_DEV_MODE)
|
||||
typedef uint16_t cutter_frequency_t;
|
||||
#define CUTTER_MENU_FREQUENCY_TYPE uint16_5
|
||||
#endif
|
||||
|
@ -59,7 +59,7 @@
|
||||
#include "../../libs/L64XX/L64XX_Marlin.h"
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_MOVE_G28_OFF)
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
#include "../../feature/spindle_laser.h"
|
||||
#endif
|
||||
|
||||
@ -205,7 +205,12 @@ void GcodeSuite::G28() {
|
||||
DEBUG_SECTION(log_G28, "G28", DEBUGGING(LEVELING));
|
||||
if (DEBUGGING(LEVELING)) log_machine_info();
|
||||
|
||||
TERN_(LASER_MOVE_G28_OFF, cutter.set_inline_enabled(false)); // turn off laser
|
||||
/*
|
||||
* Set the laser power to false to stop the planner from processing the current power setting.
|
||||
*/
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
planner.laser_inline.status.isPowered = false;
|
||||
#endif
|
||||
|
||||
#if ENABLED(DUAL_X_CARRIAGE)
|
||||
bool IDEX_saved_duplication_state = extruder_duplication_enabled;
|
||||
|
@ -31,17 +31,27 @@
|
||||
/**
|
||||
* Laser:
|
||||
* M3 - Laser ON/Power (Ramped power)
|
||||
* M4 - Laser ON/Power (Continuous power)
|
||||
* M4 - Laser ON/Power (Ramped power)
|
||||
* M5 - Set power output to 0 (leaving inline mode unchanged).
|
||||
*
|
||||
* M3I - Enable continuous inline power to be processed by the planner, with power
|
||||
* calculated and set in the planner blocks, processed inline during stepping.
|
||||
* 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.
|
||||
* M3I must be set before using planner-synced M3 inline S-Values (LASER_POWER_SYNC).
|
||||
*
|
||||
* M4I - Set dynamic mode which calculates laser power OCR based on the current feedrate.
|
||||
*
|
||||
* M5I - Clear inline mode and set power to 0.
|
||||
*
|
||||
* Spindle:
|
||||
* M3 - Spindle ON (Clockwise)
|
||||
* M4 - Spindle ON (Counter-clockwise)
|
||||
* M5 - Spindle OFF
|
||||
*
|
||||
* Parameters:
|
||||
* S<power> - Set power. S0 will turn the spindle/laser off, except in relative mode.
|
||||
* O<ocr> - Set power and OCR (oscillator count register)
|
||||
* S<power> - Set power. S0 will turn the spindle/laser off.
|
||||
*
|
||||
* If no PWM pin is defined then M3/M4 just turns it on.
|
||||
* If no PWM pin is defined then M3/M4 just turns it on or off.
|
||||
*
|
||||
* At least 12.8kHz (50Hz * 256) is needed for Spindle PWM.
|
||||
* Hardware PWM is required on AVR. ISRs are too slow.
|
||||
@ -70,77 +80,77 @@ void GcodeSuite::M3_M4(const bool is_M4) {
|
||||
reset_stepper_timeout(); // Reset timeout to allow subsequent G-code to power the laser (imm.)
|
||||
#endif
|
||||
|
||||
#if EITHER(SPINDLE_LASER_USE_PWM, SPINDLE_SERVO)
|
||||
auto get_s_power = [] {
|
||||
if (parser.seenval('S')) {
|
||||
const float spwr = parser.value_float();
|
||||
#if ENABLED(SPINDLE_SERVO)
|
||||
cutter.unitPower = spwr;
|
||||
#else
|
||||
cutter.unitPower = TERN(SPINDLE_LASER_USE_PWM,
|
||||
cutter.power_to_range(cutter_power_t(round(spwr))),
|
||||
spwr > 0 ? 255 : 0);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
cutter.unitPower = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP);
|
||||
return cutter.unitPower;
|
||||
};
|
||||
if (cutter.cutter_mode == CUTTER_MODE_STANDARD)
|
||||
planner.synchronize(); // Wait for previous movement commands (G0/G1/G2/G3) to complete before changing power
|
||||
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
if (parser.seen_test('I')) {
|
||||
cutter.cutter_mode = is_M4 ? CUTTER_MODE_DYNAMIC : CUTTER_MODE_CONTINUOUS;
|
||||
cutter.inline_power(0);
|
||||
cutter.set_enabled(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
|
||||
// Laser power in inline mode
|
||||
cutter.inline_direction(is_M4); // Should always be unused
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
if (parser.seenval('O')) {
|
||||
cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0);
|
||||
cutter.inline_ocr_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t)
|
||||
}
|
||||
else
|
||||
cutter.inline_power(cutter.upower_to_ocr(get_s_power()));
|
||||
auto get_s_power = [] {
|
||||
float u;
|
||||
if (parser.seenval('S')) {
|
||||
const float v = parser.value_float();
|
||||
u = TERN(LASER_POWER_TRAP, v, cutter.power_to_range(v));
|
||||
}
|
||||
else if (cutter.cutter_mode == CUTTER_MODE_STANDARD)
|
||||
u = cutter.cpwr_to_upwr(SPEED_POWER_STARTUP);
|
||||
|
||||
cutter.menuPower = cutter.unitPower = u;
|
||||
|
||||
// PWM not implied, power converted to OCR from unit definition and on/off if not PWM.
|
||||
cutter.power = TERN(SPINDLE_LASER_USE_PWM, cutter.upower_to_ocr(u), u > 0 ? 255 : 0);
|
||||
return u;
|
||||
};
|
||||
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS || cutter.cutter_mode == CUTTER_MODE_DYNAMIC) { // Laser power in inline mode
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
planner.laser_inline.status.isPowered = true; // M3 or M4 is powered either way
|
||||
get_s_power(); // Update cutter.power if seen
|
||||
#if ENABLED(LASER_POWER_SYNC)
|
||||
// With power sync we only set power so it does not effect queued inline power sets
|
||||
planner.buffer_sync_block(BLOCK_BIT_LASER_PWR); // Send the flag, queueing inline power
|
||||
#else
|
||||
cutter.set_inline_enabled(true);
|
||||
planner.synchronize();
|
||||
cutter.inline_power(cutter.power);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
// Non-inline, standard case
|
||||
cutter.inline_disable(); // Prevent future blocks re-setting the power
|
||||
#endif
|
||||
|
||||
planner.synchronize(); // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power
|
||||
cutter.set_reverse(is_M4);
|
||||
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
if (parser.seenval('O')) {
|
||||
cutter.unitPower = cutter.power_to_range(parser.value_byte(), 0);
|
||||
cutter.ocr_set_power(cutter.unitPower); // The OCR is a value from 0 to 255 (uint8_t)
|
||||
}
|
||||
else
|
||||
cutter.set_power(cutter.upower_to_ocr(get_s_power()));
|
||||
#elif ENABLED(SPINDLE_SERVO)
|
||||
cutter.set_power(get_s_power());
|
||||
#else
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
cutter.set_enabled(true);
|
||||
#endif
|
||||
cutter.menuPower = cutter.unitPower;
|
||||
get_s_power();
|
||||
cutter.apply_power(
|
||||
#if ENABLED(SPINDLE_SERVO)
|
||||
cutter.unitPower
|
||||
#elif ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
cutter.upower_to_ocr(cutter.unitPower)
|
||||
#else
|
||||
cutter.unitPower > 0 ? 255 : 0
|
||||
#endif
|
||||
);
|
||||
TERN_(SPINDLE_CHANGE_DIR, cutter.set_reverse(is_M4));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* M5 - Cutter OFF (when moves are complete)
|
||||
*/
|
||||
void GcodeSuite::M5() {
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
|
||||
cutter.set_inline_enabled(false); // Laser power in inline mode
|
||||
return;
|
||||
}
|
||||
// Non-inline, standard case
|
||||
cutter.inline_disable(); // Prevent future blocks re-setting the power
|
||||
#endif
|
||||
planner.synchronize();
|
||||
cutter.set_enabled(false);
|
||||
cutter.menuPower = cutter.unitPower;
|
||||
cutter.power = 0;
|
||||
cutter.apply_power(0); // M5 just kills power, leaving inline mode unchanged
|
||||
if (cutter.cutter_mode != CUTTER_MODE_STANDARD) {
|
||||
if (parser.seen_test('I')) {
|
||||
TERN_(LASER_FEATURE, cutter.inline_power(cutter.power));
|
||||
cutter.set_enabled(false); // Needs to happen while we are in inline mode to clear inline power.
|
||||
cutter.cutter_mode = CUTTER_MODE_STANDARD; // Switch from inline to standard mode.
|
||||
}
|
||||
}
|
||||
cutter.set_enabled(false); // Disable enable output setting
|
||||
}
|
||||
|
||||
#endif // HAS_CUTTER
|
||||
|
@ -53,7 +53,7 @@ GcodeSuite gcode;
|
||||
#include "../feature/cancel_object.h"
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_MOVE_POWER)
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
#include "../feature/spindle_laser.h"
|
||||
#endif
|
||||
|
||||
@ -210,8 +210,11 @@ void GcodeSuite::get_destination_from_command() {
|
||||
recovery.save();
|
||||
#endif
|
||||
|
||||
if (parser.floatval('F') > 0)
|
||||
if (parser.floatval('F') > 0) {
|
||||
feedrate_mm_s = parser.value_feedrate();
|
||||
// Update the cutter feed rate for use by M4 I set inline moves.
|
||||
TERN_(LASER_FEATURE, cutter.feedrate_mm_m = MMS_TO_MMM(feedrate_mm_s));
|
||||
}
|
||||
|
||||
#if BOTH(PRINTCOUNTER, HAS_EXTRUDERS)
|
||||
if (!DEBUGGING(DRYRUN) && !skip_move)
|
||||
@ -223,15 +226,29 @@ void GcodeSuite::get_destination_from_command() {
|
||||
M165();
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_MOVE_POWER)
|
||||
// Set the laser power in the planner to configure this move
|
||||
if (parser.seen('S')) {
|
||||
const float spwr = parser.value_float();
|
||||
cutter.inline_power(TERN(SPINDLE_LASER_USE_PWM, cutter.power_to_range(cutter_power_t(round(spwr))), spwr > 0 ? 255 : 0));
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS || cutter.cutter_mode == CUTTER_MODE_DYNAMIC) {
|
||||
// Set the cutter power in the planner to configure this move
|
||||
cutter.last_feedrate_mm_m = 0;
|
||||
if (WITHIN(parser.codenum, 1, TERN(ARC_SUPPORT, 3, 1)) || TERN0(BEZIER_CURVE_SUPPORT, parser.codenum == 5)) {
|
||||
planner.laser_inline.status.isPowered = true;
|
||||
if (parser.seen('I')) cutter.set_enabled(true); // This is set for backward LightBurn compatibility.
|
||||
if (parser.seen('S')) {
|
||||
const float v = parser.value_float(),
|
||||
u = TERN(LASER_POWER_TRAP, v, cutter.power_to_range(v));
|
||||
cutter.menuPower = cutter.unitPower = u;
|
||||
cutter.inline_power(TERN(SPINDLE_LASER_USE_PWM, cutter.upower_to_ocr(u), u > 0 ? 255 : 0));
|
||||
}
|
||||
}
|
||||
else if (parser.codenum == 0) {
|
||||
// For dynamic mode we need to flag isPowered off, dynamic power is calculated in the stepper based on feedrate.
|
||||
if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC) planner.laser_inline.status.isPowered = false;
|
||||
cutter.inline_power(0); // This is planner-based so only set power and do not disable inline control flags.
|
||||
}
|
||||
}
|
||||
else if (ENABLED(LASER_MOVE_G0_OFF) && parser.codenum == 0) // G0
|
||||
cutter.set_inline_enabled(false);
|
||||
#endif
|
||||
else if (parser.codenum == 0)
|
||||
cutter.apply_power(0);
|
||||
#endif // LASER_FEATURE
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -447,6 +447,16 @@
|
||||
#error "SPINDLE_LASER_ACTIVE_HIGH is now SPINDLE_LASER_ACTIVE_STATE."
|
||||
#elif defined(SPINDLE_LASER_ENABLE_INVERT)
|
||||
#error "SPINDLE_LASER_ENABLE_INVERT is now SPINDLE_LASER_ACTIVE_STATE."
|
||||
#elif defined(LASER_POWER_INLINE)
|
||||
#error "LASER_POWER_INLINE is not required, inline mode is enabled with 'M3 I' and disabled with 'M5 I'."
|
||||
#elif defined(LASER_POWER_INLINE_TRAPEZOID)
|
||||
#error "LASER_POWER_INLINE_TRAPEZOID is now LASER_POWER_TRAP."
|
||||
#elif defined(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
#error "LASER_POWER_INLINE_TRAPEZOID_CONT is replaced with LASER_POWER_TRAP."
|
||||
#elif defined(LASER_POWER_INLINE_TRAPEZOID_PER)
|
||||
#error "LASER_POWER_INLINE_TRAPEZOID_CONT_PER replaced with LASER_POWER_TRAP."
|
||||
#elif defined(LASER_POWER_INLINE_CONTINUOUS)
|
||||
#error "LASER_POWER_INLINE_CONTINUOUS is not required, inline mode is enabled with 'M3 I' and disabled with 'M5 I'."
|
||||
#elif defined(CUTTER_POWER_DISPLAY)
|
||||
#error "CUTTER_POWER_DISPLAY is now CUTTER_POWER_UNIT."
|
||||
#elif defined(CHAMBER_HEATER_PIN)
|
||||
@ -595,6 +605,8 @@
|
||||
#error "ARC_SUPPORT no longer uses ARC_SEGMENTS_PER_R."
|
||||
#elif ENABLED(ARC_SUPPORT) && (!defined(MIN_ARC_SEGMENT_MM) || !defined(MAX_ARC_SEGMENT_MM))
|
||||
#error "ARC_SUPPORT now requires MIN_ARC_SEGMENT_MM and MAX_ARC_SEGMENT_MM."
|
||||
#elif defined(LASER_POWER_INLINE)
|
||||
#error "LASER_POWER_INLINE is obsolete."
|
||||
#elif defined(SPINDLE_LASER_PWM)
|
||||
#error "SPINDLE_LASER_PWM (true) is now set with SPINDLE_LASER_USE_PWM (enabled)."
|
||||
#elif ANY(IS_RAMPS_EEB, IS_RAMPS_EEF, IS_RAMPS_EFB, IS_RAMPS_EFF, IS_RAMPS_SF)
|
||||
@ -3841,37 +3853,26 @@ static_assert(_PLUS_TEST(4), "HOMING_FEEDRATE_MM_M values must be positive.");
|
||||
#error "CUTTER_POWER_UNIT must be PWM255, PERCENT, RPM, or SERVO."
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||
#error "SPINDLE_CHANGE_DIR and LASER_POWER_INLINE are incompatible."
|
||||
#elif ENABLED(LASER_MOVE_G0_OFF) && DISABLED(LASER_MOVE_POWER)
|
||||
#error "LASER_MOVE_G0_OFF requires LASER_MOVE_POWER."
|
||||
#error "SPINDLE_CHANGE_DIR and LASER_FEATURE are incompatible."
|
||||
#elif ENABLED(LASER_MOVE_G0_OFF)
|
||||
#error "LASER_MOVE_G0_OFF is no longer required, G0 and G28 cannot apply power."
|
||||
#elif ENABLED(LASER_MOVE_G28_OFF)
|
||||
#error "LASER_MOVE_G0_OFF is no longer required, G0 and G28 cannot apply power."
|
||||
#elif ENABLED(LASER_MOVE_POWER)
|
||||
#error "LASER_MOVE_POWER is no longer applicable."
|
||||
#endif
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
#if ENABLED(LASER_POWER_TRAP)
|
||||
#if DISABLED(SPINDLE_LASER_USE_PWM)
|
||||
#error "LASER_POWER_INLINE_TRAPEZOID requires SPINDLE_LASER_USE_PWM to function."
|
||||
#elif ENABLED(S_CURVE_ACCELERATION)
|
||||
//#ifndef LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
|
||||
// #define LASER_POWER_INLINE_S_CURVE_ACCELERATION_WARN
|
||||
// #warning "Combining LASER_POWER_INLINE_TRAPEZOID with S_CURVE_ACCELERATION may result in unintended behavior."
|
||||
//#endif
|
||||
#error "LASER_POWER_TRAP requires SPINDLE_LASER_USE_PWM to function."
|
||||
#endif
|
||||
#endif
|
||||
#if ENABLED(LASER_POWER_INLINE_INVERT)
|
||||
//#ifndef LASER_POWER_INLINE_INVERT_WARN
|
||||
// #define LASER_POWER_INLINE_INVERT_WARN
|
||||
// #warning "Enabling LASER_POWER_INLINE_INVERT means that `M5` won't kill the laser immediately; use `M5 I` instead."
|
||||
//#endif
|
||||
#endif
|
||||
#else
|
||||
#if SPINDLE_LASER_POWERUP_DELAY < 1
|
||||
#error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0."
|
||||
#elif SPINDLE_LASER_POWERDOWN_DELAY < 1
|
||||
#error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0."
|
||||
#elif ENABLED(LASER_MOVE_POWER)
|
||||
#error "LASER_MOVE_POWER requires LASER_POWER_INLINE."
|
||||
#elif ANY(LASER_POWER_INLINE_TRAPEZOID, LASER_POWER_INLINE_INVERT, LASER_MOVE_G0_OFF, LASER_MOVE_POWER)
|
||||
#error "Enabled an inline laser feature without inline laser power being enabled."
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -3889,7 +3890,7 @@ static_assert(_PLUS_TEST(4), "HOMING_FEEDRATE_MM_M values must be positive.");
|
||||
#error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
|
||||
#elif !defined(SPINDLE_LASER_PWM_INVERT)
|
||||
#error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE."
|
||||
#elif !(defined(SPEED_POWER_INTERCEPT) && defined(SPEED_POWER_MIN) && defined(SPEED_POWER_MAX) && defined(SPEED_POWER_STARTUP))
|
||||
#elif !(defined(SPEED_POWER_MIN) && defined(SPEED_POWER_MAX) && defined(SPEED_POWER_STARTUP))
|
||||
#error "SPINDLE_LASER_USE_PWM equation constant(s) missing."
|
||||
#elif _PIN_CONFLICT(X_MIN)
|
||||
#error "SPINDLE_LASER_USE_PWM pin conflicts with X_MIN_PIN."
|
||||
|
@ -670,7 +670,7 @@ void MarlinUI::draw_status_screen() {
|
||||
|
||||
// Laser / Spindle
|
||||
#if DO_DRAW_CUTTER
|
||||
if (cutter.isReady && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
|
||||
if (cutter.isReadyForUI && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
|
||||
#if CUTTER_UNIT_IS(PERCENT)
|
||||
lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, cutter_power2str(cutter.unitPower));
|
||||
#elif CUTTER_UNIT_IS(RPM)
|
||||
|
@ -27,6 +27,10 @@
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if ENABLED(LASER_SYNCHRONOUS_M106_M107)
|
||||
#include "../../module/planner.h"
|
||||
#endif
|
||||
|
||||
void lcd_move_z();
|
||||
|
||||
////////////////////////////////////////////
|
||||
@ -538,6 +542,7 @@ class MenuItem_bool : public MenuEditItemBase {
|
||||
|
||||
inline void on_fan_update() {
|
||||
thermalManager.set_fan_speed(MenuItemBase::itemIndex, editable.uint8);
|
||||
TERN_(LASER_SYNCHRONOUS_M106_M107, planner.buffer_sync_block(BLOCK_FLAG_SYNC_FANS));
|
||||
}
|
||||
|
||||
#if ENABLED(EXTRA_FAN_SPEED)
|
||||
|
@ -33,7 +33,7 @@
|
||||
#include "../../feature/spindle_laser.h"
|
||||
|
||||
void menu_spindle_laser() {
|
||||
bool is_enabled = cutter.enabled() && cutter.isReady;
|
||||
bool is_enabled = cutter.enabled();
|
||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||
bool is_rev = cutter.is_reverse();
|
||||
#endif
|
||||
@ -49,7 +49,13 @@
|
||||
#endif
|
||||
|
||||
editable.state = is_enabled;
|
||||
EDIT_ITEM(bool, MSG_CUTTER(TOGGLE), &is_enabled, []{ if (editable.state) cutter.disable(); else cutter.enable_same_dir(); });
|
||||
EDIT_ITEM(bool, MSG_CUTTER(TOGGLE), &is_enabled, []{
|
||||
#if ENABLED(SPINDLE_FEATURE)
|
||||
if (editable.state) cutter.disable(); else cutter.enable_same_dir();
|
||||
#else
|
||||
cutter.laser_menu_toggle(!editable.state);
|
||||
#endif
|
||||
});
|
||||
|
||||
#if ENABLED(AIR_EVACUATION)
|
||||
bool evac_state = cutter.air_evac_state();
|
||||
@ -72,12 +78,10 @@
|
||||
// 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.
|
||||
EDIT_ITEM_FAST(CUTTER_MENU_PULSE_TYPE, MSG_LASER_PULSE_MS, &cutter.testPulse, LASER_TEST_PULSE_MIN, LASER_TEST_PULSE_MAX);
|
||||
ACTION_ITEM(MSG_LASER_FIRE_PULSE, cutter.test_fire_pulse);
|
||||
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
|
||||
EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 80000, cutter.refresh_frequency);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if BOTH(MARLIN_DEV_MODE, HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY
|
||||
EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 80000, cutter.refresh_frequency);
|
||||
#endif
|
||||
|
||||
END_MENU();
|
||||
}
|
||||
|
||||
|
@ -128,8 +128,13 @@ uint8_t Planner::delay_before_delivering; // This counter delays delivery
|
||||
|
||||
planner_settings_t Planner::settings; // Initialized by settings.load()
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
/**
|
||||
* Set up inline block variables
|
||||
* Set laser_power_floor based on SPEED_POWER_MIN to pevent a zero power output state with LASER_POWER_TRAP
|
||||
*/
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
laser_state_t Planner::laser_inline; // Current state for blocks
|
||||
const uint8_t laser_power_floor = cutter.pct_to_ocr(SPEED_POWER_MIN);
|
||||
#endif
|
||||
|
||||
uint32_t Planner::max_acceleration_steps_per_s2[DISTINCT_AXES]; // (steps/s^2) Derived from mm_per_s2
|
||||
@ -799,6 +804,7 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
|
||||
if (plateau_steps < 0) {
|
||||
const float accelerate_steps_float = CEIL(intersection_distance(initial_rate, final_rate, accel, block->step_event_count));
|
||||
accelerate_steps = _MIN(uint32_t(_MAX(accelerate_steps_float, 0)), block->step_event_count);
|
||||
decelerate_steps = block->step_event_count - accelerate_steps;
|
||||
plateau_steps = 0;
|
||||
|
||||
#if ENABLED(S_CURVE_ACCELERATION)
|
||||
@ -822,7 +828,7 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
|
||||
|
||||
// Store new block parameters
|
||||
block->accelerate_until = accelerate_steps;
|
||||
block->decelerate_after = accelerate_steps + plateau_steps;
|
||||
block->decelerate_after = block->step_event_count - decelerate_steps;
|
||||
block->initial_rate = initial_rate;
|
||||
#if ENABLED(S_CURVE_ACCELERATION)
|
||||
block->acceleration_time = acceleration_time;
|
||||
@ -833,46 +839,52 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
|
||||
#endif
|
||||
block->final_rate = final_rate;
|
||||
|
||||
/**
|
||||
* Laser trapezoid calculations
|
||||
*
|
||||
* Approximate the trapezoid with the laser, incrementing the power every `entry_per` while accelerating
|
||||
* and decrementing it every `exit_power_per` while decelerating, thus ensuring power is related to feedrate.
|
||||
*
|
||||
* LASER_POWER_INLINE_TRAPEZOID_CONT doesn't need this as it continuously approximates
|
||||
*
|
||||
* Note this may behave unreliably when running with S_CURVE_ACCELERATION
|
||||
*/
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
if (block->laser.power > 0) { // No need to care if power == 0
|
||||
const uint8_t entry_power = block->laser.power * entry_factor; // Power on block entry
|
||||
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
// Speedup power
|
||||
const uint8_t entry_power_diff = block->laser.power - entry_power;
|
||||
if (entry_power_diff) {
|
||||
block->laser.entry_per = accelerate_steps / entry_power_diff;
|
||||
block->laser.power_entry = entry_power;
|
||||
#if ENABLED(LASER_POWER_TRAP)
|
||||
/**
|
||||
* Laser Trapezoid Calculations
|
||||
*
|
||||
* Approximate the trapezoid with the laser, incrementing the power every `trap_ramp_entry_incr` steps while accelerating,
|
||||
* and decrementing the power every `trap_ramp_exit_decr` while decelerating, to keep power proportional to feedrate.
|
||||
* Laser power trap will reduce the initial power to no less than the laser_power_floor value. Based on the number
|
||||
* of calculated accel/decel steps the power is distributed over the trapezoid entry- and exit-ramp steps.
|
||||
*
|
||||
* trap_ramp_active_pwr - The active power is initially set at a reduced level factor of initial power / accel steps and
|
||||
* will be additively incremented using a trap_ramp_entry_incr value for each accel step processed later in the stepper code.
|
||||
* The trap_ramp_exit_decr value is calculated as power / decel steps and is also adjusted to no less than the power floor.
|
||||
*
|
||||
* If the power == 0 the inline mode variables need to be set to zero to prevent stepper processing. The method allows
|
||||
* for simpler non-powered moves like G0 or G28.
|
||||
*
|
||||
* Laser Trap Power works for all Jerk and Curve modes; however Arc-based moves will have issues since the segments are
|
||||
* usually too small.
|
||||
*/
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
|
||||
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
|
||||
if (block->laser.power > 0) {
|
||||
NOLESS(block->laser.power, laser_power_floor);
|
||||
block->laser.trap_ramp_active_pwr = (block->laser.power - laser_power_floor) * (initial_rate / float(block->nominal_rate)) + laser_power_floor;
|
||||
block->laser.trap_ramp_entry_incr = (block->laser.power - block->laser.trap_ramp_active_pwr) / accelerate_steps;
|
||||
float laser_pwr = block->laser.power * (final_rate / float(block->nominal_rate));
|
||||
NOLESS(laser_pwr, laser_power_floor);
|
||||
block->laser.trap_ramp_exit_decr = (block->laser.power - laser_pwr) / decelerate_steps;
|
||||
#if ENABLED(DEBUG_LASER_TRAP)
|
||||
SERIAL_ECHO_MSG("lp:",block->laser.power);
|
||||
SERIAL_ECHO_MSG("as:",accelerate_steps);
|
||||
SERIAL_ECHO_MSG("ds:",decelerate_steps);
|
||||
SERIAL_ECHO_MSG("p.trap:",block->laser.trap_ramp_active_pwr);
|
||||
SERIAL_ECHO_MSG("p.incr:",block->laser.trap_ramp_entry_incr);
|
||||
SERIAL_ECHO_MSG("p.decr:",block->laser.trap_ramp_exit_decr);
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
block->laser.entry_per = 0;
|
||||
block->laser.power_entry = block->laser.power;
|
||||
block->laser.trap_ramp_active_pwr = 0;
|
||||
block->laser.trap_ramp_entry_incr = 0;
|
||||
block->laser.trap_ramp_exit_decr = 0;
|
||||
}
|
||||
// Slowdown power
|
||||
const uint8_t exit_power = block->laser.power * exit_factor, // Power on block entry
|
||||
exit_power_diff = block->laser.power - exit_power;
|
||||
if (exit_power_diff) {
|
||||
block->laser.exit_per = (block->step_event_count - block->decelerate_after) / exit_power_diff;
|
||||
block->laser.power_exit = exit_power;
|
||||
}
|
||||
else {
|
||||
block->laser.exit_per = 0;
|
||||
block->laser.power_exit = block->laser.power;
|
||||
}
|
||||
#else
|
||||
block->laser.power_entry = entry_power;
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // LASER_POWER_TRAP
|
||||
}
|
||||
|
||||
/* PLANNER SPEED DEFINITION
|
||||
@ -1130,10 +1142,9 @@ void Planner::recalculate_trapezoids() {
|
||||
// The tail may be changed by the ISR so get a local copy.
|
||||
uint8_t block_index = block_buffer_tail,
|
||||
head_block_index = block_buffer_head;
|
||||
|
||||
// Since there could be non-move blocks in the head of the queue, and the
|
||||
// Since there could be a sync block in the head of the queue, and the
|
||||
// next loop must not recalculate the head block (as it needs to be
|
||||
// specially handled), scan backwards to the first move block.
|
||||
// specially handled), scan backwards to the first non-SYNC block.
|
||||
while (head_block_index != block_index) {
|
||||
|
||||
// Go back (head always point to the first free block)
|
||||
@ -1203,7 +1214,7 @@ void Planner::recalculate_trapezoids() {
|
||||
// Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated.
|
||||
if (next) {
|
||||
|
||||
// Mark the last block as RECALCULATE, to prevent the Stepper ISR running it.
|
||||
// Mark the next(last) block as RECALCULATE, to prevent the Stepper ISR running it.
|
||||
// As the last block is always recalculated here, there is a chance the block isn't
|
||||
// marked as RECALCULATE yet. That's the reason for the following line.
|
||||
block->flag.recalculate = true;
|
||||
@ -1295,7 +1306,7 @@ void Planner::recalculate() {
|
||||
#endif // HAS_FAN
|
||||
|
||||
/**
|
||||
* Maintain fans, paste extruder pressure,
|
||||
* Maintain fans, paste extruder pressure, spindle/laser power
|
||||
*/
|
||||
void Planner::check_axes_activity() {
|
||||
|
||||
@ -1359,7 +1370,7 @@ void Planner::check_axes_activity() {
|
||||
}
|
||||
else {
|
||||
|
||||
TERN_(HAS_CUTTER, cutter.refresh());
|
||||
TERN_(HAS_CUTTER, if (cutter.cutter_mode == CUTTER_MODE_STANDARD) cutter.refresh());
|
||||
|
||||
#if HAS_TAIL_FAN_SPEED
|
||||
FANS_LOOP(i) {
|
||||
@ -1459,7 +1470,7 @@ void Planner::check_axes_activity() {
|
||||
for (uint8_t b = block_buffer_tail; b != block_buffer_head; b = next_block_index(b)) {
|
||||
const block_t * const block = &block_buffer[b];
|
||||
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)) {
|
||||
const float se = (float)block->steps.e / block->step_event_count * SQRT(block->nominal_speed_sqr); // mm/sec
|
||||
const float se = (float)block->steps.e / block->step_event_count * SQRT(block->nominal_speed_sqr); // mm/sec;
|
||||
NOLESS(high, se);
|
||||
}
|
||||
}
|
||||
@ -1781,7 +1792,7 @@ void Planner::synchronize() { while (busy()) idle(); }
|
||||
bool Planner::_buffer_steps(const xyze_long_t &target
|
||||
OPTARG(HAS_POSITION_FLOAT, const xyze_pos_t &target_float)
|
||||
OPTARG(HAS_DIST_MM_ARG, const xyze_float_t &cart_dist_mm)
|
||||
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters/*=0.0*/
|
||||
, feedRate_t fr_mm_s, const uint8_t extruder, const_float_t millimeters
|
||||
) {
|
||||
|
||||
// Wait for the next available block
|
||||
@ -1863,8 +1874,36 @@ bool Planner::_populate_block(
|
||||
);
|
||||
|
||||
/* <-- add a slash to enable
|
||||
#define _ALINE(A) " " STR_##A ":", target[_AXIS(A)], " (", int32_t(target[_AXIS(A)] - position[_AXIS(A)]), " steps)"
|
||||
SERIAL_ECHOLNPGM(" _populate_block FR:", fr_mm_s, LOGICAL_AXIS_MAP(_ALINE));
|
||||
SERIAL_ECHOLNPGM(
|
||||
" _populate_block FR:", fr_mm_s,
|
||||
" A:", target.a, " (", da, " steps)"
|
||||
#if HAS_Y_AXIS
|
||||
" B:", target.b, " (", db, " steps)"
|
||||
#endif
|
||||
#if HAS_Z_AXIS
|
||||
" C:", target.c, " (", dc, " steps)"
|
||||
#endif
|
||||
#if HAS_I_AXIS
|
||||
" " STR_I ":", target.i, " (", di, " steps)"
|
||||
#endif
|
||||
#if HAS_J_AXIS
|
||||
" " STR_J ":", target.j, " (", dj, " steps)"
|
||||
#endif
|
||||
#if HAS_K_AXIS
|
||||
" " STR_K ":", target.k, " (", dk, " steps)"
|
||||
#endif
|
||||
#if HAS_U_AXIS
|
||||
" " STR_U ":", target.u, " (", du, " steps)"
|
||||
#endif
|
||||
#if HAS_V_AXIS
|
||||
" " STR_V ":", target.v, " (", dv, " steps)"
|
||||
#endif
|
||||
#if HAS_W_AXIS
|
||||
" " STR_W ":", target.w, " (", dw, " steps)"
|
||||
#if HAS_EXTRUDERS
|
||||
" E:", target.e, " (", de, " steps)"
|
||||
#endif
|
||||
);
|
||||
//*/
|
||||
|
||||
#if EITHER(PREVENT_COLD_EXTRUSION, PREVENT_LENGTHY_EXTRUDE)
|
||||
@ -1962,11 +2001,34 @@ bool Planner::_populate_block(
|
||||
// Set direction bits
|
||||
block->direction_bits = dm;
|
||||
|
||||
// Update block laser power
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
laser_inline.status.isPlanned = true;
|
||||
block->laser.status = laser_inline.status;
|
||||
block->laser.power = laser_inline.power;
|
||||
/**
|
||||
* Update block laser power
|
||||
* For standard mode get the cutter.power value for processing, since it's
|
||||
* only set by apply_power().
|
||||
*/
|
||||
#if HAS_CUTTER
|
||||
switch (cutter.cutter_mode) {
|
||||
default: break;
|
||||
|
||||
case CUTTER_MODE_STANDARD: block->cutter_power = cutter.power; break;
|
||||
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
/**
|
||||
* For inline mode get the laser_inline variables, including power and status.
|
||||
* Dynamic mode only needs to update if the feedrate has changed, since it's
|
||||
* calculated from the current feedrate and power level.
|
||||
*/
|
||||
case CUTTER_MODE_CONTINUOUS:
|
||||
block->laser.power = laser_inline.power;
|
||||
block->laser.status = laser_inline.status;
|
||||
break;
|
||||
|
||||
case CUTTER_MODE_DYNAMIC:
|
||||
if (cutter.laser_feedrate_changed()) // Only process changes in rate
|
||||
block->laser.power = laser_inline.power = cutter.calc_dynamic_power();
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// Number of steps for each axis
|
||||
@ -2028,9 +2090,9 @@ bool Planner::_populate_block(
|
||||
#endif
|
||||
#elif ENABLED(MARKFORGED_XY)
|
||||
steps_dist_mm.a = (da - db) * mm_per_step[A_AXIS];
|
||||
steps_dist_mm.b = db * mm_per_step[B_AXIS];
|
||||
steps_dist_mm.b = db * mm_per_step[B_AXIS];
|
||||
#elif ENABLED(MARKFORGED_YX)
|
||||
steps_dist_mm.a = da * mm_per_step[A_AXIS];
|
||||
steps_dist_mm.a = da * mm_per_step[A_AXIS];
|
||||
steps_dist_mm.b = (db - da) * mm_per_step[B_AXIS];
|
||||
#else
|
||||
XYZ_CODE(
|
||||
@ -2076,21 +2138,12 @@ bool Planner::_populate_block(
|
||||
block->millimeters = millimeters;
|
||||
else {
|
||||
/**
|
||||
* Distance for interpretation of feedrate in accordance with LinuxCNC (the successor of
|
||||
* NIST RS274NGC interpreter - version 3) and its default CANON_XYZ feed reference mode.
|
||||
*
|
||||
* Assume:
|
||||
* - X, Y, Z are the primary linear axes;
|
||||
* - U, V, W are secondary linear axes;
|
||||
* - A, B, C are rotational axes.
|
||||
*
|
||||
* Then:
|
||||
* - dX, dY, dZ are the displacements of the primary linear axes;
|
||||
* - dU, dV, dW are the displacements of linear axes;
|
||||
* - dA, dB, dC are the displacements of rotational axes.
|
||||
*
|
||||
* The time it takes to execute move command with feedrate F is t = D/F,
|
||||
* where D is the total distance, calculated as follows:
|
||||
* Distance for interpretation of feedrate in accordance with LinuxCNC (the successor of NIST
|
||||
* RS274NGC interpreter - version 3) and its default CANON_XYZ feed reference mode.
|
||||
* Assume that X, Y, Z are the primary linear axes and U, V, W are secondary linear axes and A, B, C are
|
||||
* 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
|
||||
* dA, dB, dC are the displacements of rotational axes.
|
||||
* The time it takes to execute move command with feedrate F is t = D/F, where D is the total distance, calculated as follows:
|
||||
* D^2 = dX^2 + dY^2 + dZ^2
|
||||
* if D^2 == 0 (none of XYZ move but any secondary linear axes move, whether other axes are moved or not):
|
||||
* D^2 = dU^2 + dV^2 + dW^2
|
||||
@ -2099,9 +2152,8 @@ bool Planner::_populate_block(
|
||||
*/
|
||||
float distance_sqr = (
|
||||
#if ENABLED(ARTICULATED_ROBOT_ARM)
|
||||
// For articulated robots, interpreting feedrate like LinuxCNC would require inverse kinematics. As a workaround,
|
||||
// assume that motors sit on a mutually-orthogonal axes and we can think of distance as magnitude of an n-vector
|
||||
// in an n-dimensional Euclidian space.
|
||||
// For articulated robots, interpreting feedrate like LinuxCNC would require inverse kinematics. As a workaround, pretend that motors sit on n mutually orthogonal
|
||||
// axes and assume that we could think of distance as magnitude of an n-vector in an n-dimensional Euclidian space.
|
||||
NUM_AXIS_GANG(
|
||||
sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z),
|
||||
+ sq(steps_dist_mm.i), + sq(steps_dist_mm.j), + sq(steps_dist_mm.k),
|
||||
@ -2121,7 +2173,7 @@ bool Planner::_populate_block(
|
||||
#elif CORE_IS_YZ
|
||||
XYZ_GANG(sq(steps_dist_mm.x), + sq(steps_dist_mm.head.y), + sq(steps_dist_mm.head.z))
|
||||
#else
|
||||
XYZ_GANG(sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z))
|
||||
XYZ_GANG(sq(steps_dist_mm.x), + sq(steps_dist_mm.y), + sq(steps_dist_mm.z))
|
||||
#endif
|
||||
);
|
||||
|
||||
@ -2154,9 +2206,9 @@ bool Planner::_populate_block(
|
||||
|
||||
/**
|
||||
* At this point at least one of the axes has more steps than
|
||||
* MIN_STEPS_PER_SEGMENT, ensuring the segment won't get dropped
|
||||
* as zero-length. It's important to not apply corrections to blocks
|
||||
* that would get dropped!
|
||||
* MIN_STEPS_PER_SEGMENT, ensuring the segment won't get dropped as
|
||||
* zero-length. It's important to not apply corrections
|
||||
* to blocks that would get dropped!
|
||||
*
|
||||
* A correction function is permitted to add steps to an axis, it
|
||||
* should *never* remove steps!
|
||||
@ -2177,7 +2229,6 @@ bool Planner::_populate_block(
|
||||
|
||||
TERN_(MIXING_EXTRUDER, mixer.populate_block(block->b_color));
|
||||
|
||||
TERN_(HAS_CUTTER, block->cutter_power = cutter.power);
|
||||
|
||||
#if HAS_FAN
|
||||
FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
|
||||
@ -2192,9 +2243,15 @@ bool Planner::_populate_block(
|
||||
|
||||
#if ENABLED(AUTO_POWER_CONTROL)
|
||||
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
|
||||
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
|
||||
)) powerManager.power_on();
|
||||
#endif
|
||||
|
||||
@ -2428,7 +2485,7 @@ bool Planner::_populate_block(
|
||||
if (speed_factor < 1.0f) {
|
||||
current_speed *= speed_factor;
|
||||
block->nominal_rate *= speed_factor;
|
||||
block->nominal_speed_sqr *= sq(speed_factor);
|
||||
block->nominal_speed_sqr = block->nominal_speed_sqr * sq(speed_factor);
|
||||
}
|
||||
|
||||
// Compute and limit the acceleration rate for the trapezoid generator.
|
||||
@ -2630,15 +2687,14 @@ bool Planner::_populate_block(
|
||||
vmax_junction_sqr = sq(float(MINIMUM_PLANNER_SPEED));
|
||||
}
|
||||
else {
|
||||
NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
|
||||
|
||||
// Convert delta vector to unit vector
|
||||
xyze_float_t junction_unit_vec = unit_vec - prev_unit_vec;
|
||||
normalize_junction_vector(junction_unit_vec);
|
||||
|
||||
const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec);
|
||||
|
||||
NOLESS(junction_cos_theta, -0.999999f); // Check for numerical round-off to avoid divide by zero.
|
||||
|
||||
const float sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
|
||||
const float junction_acceleration = limit_value_by_axis_maximum(block->acceleration, junction_unit_vec),
|
||||
sin_theta_d2 = SQRT(0.5f * (1.0f - junction_cos_theta)); // Trig half angle identity. Always positive.
|
||||
|
||||
vmax_junction_sqr = junction_acceleration * junction_deviation_mm * sin_theta_d2 / (1.0f - sin_theta_d2);
|
||||
|
||||
@ -2889,21 +2945,19 @@ bool Planner::_populate_block(
|
||||
|
||||
/**
|
||||
* Planner::buffer_sync_block
|
||||
* Add a block to the buffer that just updates the position,
|
||||
* or in case of LASER_SYNCHRONOUS_M106_M107 the fan PWM
|
||||
* Add a block to the buffer that just updates the position
|
||||
* @param sync_flag BLOCK_FLAG_SYNC_FANS & BLOCK_FLAG_LASER_PWR
|
||||
* Supports LASER_SYNCHRONOUS_M106_M107 and LASER_POWER_SYNC power sync block buffer queueing.
|
||||
*/
|
||||
void Planner::buffer_sync_block(TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/)) {
|
||||
#if DISABLED(LASER_SYNCHRONOUS_M106_M107)
|
||||
constexpr BlockFlagBit sync_flag = BLOCK_BIT_SYNC_POSITION;
|
||||
#endif
|
||||
|
||||
void Planner::buffer_sync_block(const BlockFlagBit sync_flag/*=BLOCK_BIT_SYNC_POSITION*/) {
|
||||
|
||||
// Wait for the next available block
|
||||
uint8_t next_buffer_head;
|
||||
block_t * const block = get_next_free_block(next_buffer_head);
|
||||
|
||||
// Clear block
|
||||
block->reset();
|
||||
|
||||
memset(block, 0, sizeof(block_t));
|
||||
block->flag.apply(sync_flag);
|
||||
|
||||
block->position = position;
|
||||
@ -2915,6 +2969,12 @@ void Planner::buffer_sync_block(TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFl
|
||||
FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
|
||||
#endif
|
||||
|
||||
/**
|
||||
* M3-based power setting can be processed inline with a laser power sync block.
|
||||
* During active moves cutter.power is processed immediately, otherwise on the next move.
|
||||
*/
|
||||
TERN_(LASER_POWER_SYNC, block->laser.power = cutter.power);
|
||||
|
||||
// If this is the first added movement, reload the delay, otherwise, cancel it.
|
||||
if (block_buffer_head == block_buffer_tail) {
|
||||
// If it was the first queued block, restart the 1st block delivery delay, to
|
||||
@ -3052,8 +3112,8 @@ bool Planner::buffer_segment(const abce_pos_t &abce
|
||||
if (!_buffer_steps(target
|
||||
OPTARG(HAS_POSITION_FLOAT, target_float)
|
||||
OPTARG(HAS_DIST_MM_ARG, cart_dist_mm)
|
||||
, fr_mm_s, extruder, millimeters
|
||||
)) return false;
|
||||
, fr_mm_s, extruder, millimeters)
|
||||
) return false;
|
||||
|
||||
stepper.wake_up();
|
||||
return true;
|
||||
@ -3099,7 +3159,7 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, cons
|
||||
inverse_kinematics(machine);
|
||||
|
||||
#if ENABLED(SCARA_FEEDRATE_SCALING)
|
||||
// For SCARA scale the feed rate from mm/s to degrees/s
|
||||
// For SCARA scale the feedrate from mm/s to degrees/s
|
||||
// i.e., Complete the angular vector in the given time.
|
||||
const float duration_recip = inv_duration ?: fr_mm_s / mm;
|
||||
const xyz_pos_t diff = delta - position_float;
|
||||
@ -3120,14 +3180,6 @@ bool Planner::buffer_line(const xyze_pos_t &cart, const_feedRate_t fr_mm_s, cons
|
||||
|
||||
#if ENABLED(DIRECT_STEPPING)
|
||||
|
||||
/**
|
||||
* @brief Add a direct stepping page block to the buffer
|
||||
* and wake up the Stepper ISR to process it.
|
||||
*
|
||||
* @param page_idx Page index provided by G6 I<index>
|
||||
* @param extruder The extruder to use in the move
|
||||
* @param num_steps Number of steps to process in the ISR
|
||||
*/
|
||||
void Planner::buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps) {
|
||||
if (!last_page_step_rate) {
|
||||
kill(GET_TEXT_F(MSG_BAD_PAGE_SPEED));
|
||||
@ -3212,7 +3264,7 @@ void Planner::set_machine_position_mm(const abce_pos_t &abce) {
|
||||
if (has_blocks_queued()) {
|
||||
//previous_nominal_speed_sqr = 0.0; // Reset planner junction speeds. Assume start from rest.
|
||||
//previous_speed.reset();
|
||||
buffer_sync_block();
|
||||
buffer_sync_block(BLOCK_BIT_SYNC_POSITION);
|
||||
}
|
||||
else {
|
||||
#if ENABLED(BACKLASH_COMPENSATION)
|
||||
@ -3225,12 +3277,6 @@ void Planner::set_machine_position_mm(const abce_pos_t &abce) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the Planner position in mm
|
||||
* @details Set the Planner position from a native machine position in mm
|
||||
*
|
||||
* @param xyze A native (Cartesian) machine position
|
||||
*/
|
||||
void Planner::set_position_mm(const xyze_pos_t &xyze) {
|
||||
xyze_pos_t machine = xyze;
|
||||
TERN_(HAS_POSITION_MODIFIERS, apply_modifiers(machine, true));
|
||||
@ -3259,20 +3305,14 @@ void Planner::set_position_mm(const xyze_pos_t &xyze) {
|
||||
TERN_(IS_KINEMATIC, TERN_(HAS_EXTRUDERS, position_cart.e = e));
|
||||
|
||||
if (has_blocks_queued())
|
||||
buffer_sync_block();
|
||||
buffer_sync_block(BLOCK_BIT_SYNC_POSITION);
|
||||
else
|
||||
stepper.set_axis_position(E_AXIS, position.e);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
|
||||
* @details Update planner movement factors after a change to certain settings:
|
||||
* - max_acceleration_steps_per_s2 from settings max_acceleration_mm_per_s2 * axis_steps_per_mm (M201, M92)
|
||||
* - acceleration_long_cutoff based on the largest max_acceleration_steps_per_s2 (M201)
|
||||
* - max_e_jerk for all extruders based on junction_deviation_mm (M205 J)
|
||||
*/
|
||||
// Recalculate the steps/s^2 acceleration rates, based on the mm/s^2
|
||||
void Planner::refresh_acceleration_rates() {
|
||||
uint32_t highest_rate = 1;
|
||||
LOOP_DISTINCT_AXES(i) {
|
||||
@ -3285,8 +3325,8 @@ void Planner::refresh_acceleration_rates() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Recalculate 'position' and 'mm_per_step'.
|
||||
* @details Required whenever settings.axis_steps_per_mm changes!
|
||||
* Recalculate 'position' and 'mm_per_step'.
|
||||
* Must be called whenever settings.axis_steps_per_mm changes!
|
||||
*/
|
||||
void Planner::refresh_positioning() {
|
||||
LOOP_DISTINCT_AXES(i) mm_per_step[i] = 1.0f / settings.axis_steps_per_mm[i];
|
||||
|
@ -89,30 +89,6 @@
|
||||
#define HAS_DIST_MM_ARG 1
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
|
||||
typedef struct {
|
||||
bool isPlanned:1;
|
||||
bool isEnabled:1;
|
||||
bool dir:1;
|
||||
bool Reserved:6;
|
||||
} power_status_t;
|
||||
|
||||
typedef struct {
|
||||
power_status_t status; // See planner settings for meaning
|
||||
uint8_t power; // Ditto; When in trapezoid mode this is nominal power
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
uint8_t power_entry; // Entry power for the laser
|
||||
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
uint8_t power_exit; // Exit power for the laser
|
||||
uint32_t entry_per, // Steps per power increment (to avoid floats in stepper calcs)
|
||||
exit_per; // Steps per power decrement
|
||||
#endif
|
||||
#endif
|
||||
} block_laser_t;
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Planner block flags as boolean bit fields
|
||||
*/
|
||||
@ -132,14 +108,14 @@ enum BlockFlagBit {
|
||||
BLOCK_BIT_SYNC_POSITION
|
||||
|
||||
// Direct stepping page
|
||||
#if ENABLED(DIRECT_STEPPING)
|
||||
, BLOCK_BIT_PAGE
|
||||
#endif
|
||||
OPTARG(DIRECT_STEPPING, BLOCK_BIT_PAGE)
|
||||
|
||||
|
||||
// Sync the fan speeds from the block
|
||||
#if ENABLED(LASER_SYNCHRONOUS_M106_M107)
|
||||
, BLOCK_BIT_SYNC_FANS
|
||||
#endif
|
||||
OPTARG(LASER_SYNCHRONOUS_M106_M107, BLOCK_BIT_SYNC_FANS)
|
||||
|
||||
// Sync laser power from a queued block
|
||||
OPTARG(LASER_POWER_SYNC, BLOCK_BIT_LASER_PWR)
|
||||
};
|
||||
|
||||
/**
|
||||
@ -165,6 +141,10 @@ typedef struct {
|
||||
#if ENABLED(LASER_SYNCHRONOUS_M106_M107)
|
||||
bool sync_fans:1;
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_POWER_SYNC)
|
||||
bool sync_laser_pwr:1;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
@ -176,9 +156,34 @@ typedef struct {
|
||||
|
||||
} block_flags_t;
|
||||
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
|
||||
typedef struct {
|
||||
bool isEnabled:1; // Set to engage the inline laser power output.
|
||||
bool dir:1;
|
||||
bool isPowered:1; // Set on any parsed G1, G2, G3, or G5 powered move, cleared on G0 and G28.
|
||||
bool isSyncPower:1; // Set on a M3 sync based set laser power, used to determine active trap power
|
||||
bool Reserved:4;
|
||||
} power_status_t;
|
||||
|
||||
typedef struct {
|
||||
power_status_t status; // See planner settings for meaning
|
||||
uint8_t power; // Ditto; When in trapezoid mode this is nominal power
|
||||
|
||||
#if ENABLED(LASER_POWER_TRAP)
|
||||
float trap_ramp_active_pwr; // Laser power level during active trapezoid smoothing
|
||||
float trap_ramp_entry_incr; // Acceleration per step laser power increment (trap entry)
|
||||
float trap_ramp_exit_decr; // Deceleration per step laser power decrement (trap exit)
|
||||
#endif
|
||||
} block_laser_t;
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A single entry in the planner buffer, used to set up and
|
||||
* track a coordinated linear motion for one or more axes.
|
||||
* struct block_t
|
||||
*
|
||||
* A single entry in the planner buffer.
|
||||
* Tracks linear movement over multiple axes.
|
||||
*
|
||||
* The "nominal" values are as-specified by G-code, and
|
||||
* may never actually be reached due to acceleration limits.
|
||||
@ -188,7 +193,8 @@ typedef struct block_t {
|
||||
volatile block_flags_t flag; // Block flags
|
||||
|
||||
volatile bool is_fan_sync() { return TERN0(LASER_SYNCHRONOUS_M106_M107, flag.sync_fans); }
|
||||
volatile bool is_sync() { return flag.sync_position || is_fan_sync(); }
|
||||
volatile bool is_pwr_sync() { return TERN0(LASER_POWER_SYNC, flag.sync_laser_pwr); }
|
||||
volatile bool is_sync() { return flag.sync_position || is_fan_sync() || is_pwr_sync(); }
|
||||
volatile bool is_page() { return TERN0(DIRECT_STEPPING, flag.page); }
|
||||
volatile bool is_move() { return !(is_sync() || is_page()); }
|
||||
|
||||
@ -270,12 +276,10 @@ typedef struct block_t {
|
||||
xyze_pos_t start_position;
|
||||
#endif
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
block_laser_t laser;
|
||||
#endif
|
||||
|
||||
void reset() { memset((char*)this, 0, sizeof(*this)); }
|
||||
|
||||
} block_t;
|
||||
|
||||
#if ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL, POWER_LOSS_RECOVERY)
|
||||
@ -284,7 +288,7 @@ typedef struct block_t {
|
||||
|
||||
#define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
typedef struct {
|
||||
/**
|
||||
* Laser status flags
|
||||
@ -293,11 +297,10 @@ typedef struct block_t {
|
||||
/**
|
||||
* Laser power: 0 or 255 in case of PWM-less laser,
|
||||
* or the OCR (oscillator count register) value;
|
||||
*
|
||||
* Using OCR instead of raw power, because it avoids
|
||||
* floating point operations during the move loop.
|
||||
*/
|
||||
uint8_t power;
|
||||
volatile uint8_t power;
|
||||
} laser_state_t;
|
||||
#endif
|
||||
|
||||
@ -399,7 +402,7 @@ class Planner {
|
||||
|
||||
static planner_settings_t settings;
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
static laser_state_t laser_inline;
|
||||
#endif
|
||||
|
||||
@ -784,12 +787,11 @@ class Planner {
|
||||
|
||||
/**
|
||||
* Planner::buffer_sync_block
|
||||
* Add a block to the buffer that just updates the position or in
|
||||
* case of LASER_SYNCHRONOUS_M106_M107 the fan pwm
|
||||
* Add a block to the buffer that just updates the position
|
||||
* @param sync_flag sets a condition bit to process additional items
|
||||
* such as sync fan pwm or sync M3/M4 laser power into a queued block
|
||||
*/
|
||||
static void buffer_sync_block(
|
||||
TERN_(LASER_SYNCHRONOUS_M106_M107, const BlockFlagBit flag=BLOCK_BIT_SYNC_POSITION)
|
||||
);
|
||||
static void buffer_sync_block(const BlockFlagBit flag=BLOCK_BIT_SYNC_POSITION);
|
||||
|
||||
#if IS_KINEMATIC
|
||||
private:
|
||||
|
@ -253,20 +253,6 @@ xyz_long_t Stepper::endstops_trigsteps;
|
||||
xyze_long_t Stepper::count_position{0};
|
||||
xyze_int8_t Stepper::count_direction{0};
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
Stepper::stepper_laser_t Stepper::laser_trap = {
|
||||
.enabled = false,
|
||||
.cur_power = 0,
|
||||
.cruise_set = false,
|
||||
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
.last_step_count = 0,
|
||||
.acc_step_count = 0
|
||||
#else
|
||||
.till_update = 0
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
#define MINDIR(A) (count_direction[_AXIS(A)] < 0)
|
||||
#define MAXDIR(A) (count_direction[_AXIS(A)] > 0)
|
||||
|
||||
@ -1964,7 +1950,6 @@ uint32_t Stepper::block_phase_isr() {
|
||||
|
||||
// If there is a current block
|
||||
if (current_block) {
|
||||
|
||||
// If current block is finished, reset pointer and finalize state
|
||||
if (step_events_completed >= step_event_count) {
|
||||
#if ENABLED(DIRECT_STEPPING)
|
||||
@ -2017,32 +2002,28 @@ uint32_t Stepper::block_phase_isr() {
|
||||
else if (LA_steps) nextAdvanceISR = 0;
|
||||
#endif
|
||||
|
||||
// Update laser - Accelerating
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
if (laser_trap.enabled) {
|
||||
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
if (current_block->laser.entry_per) {
|
||||
laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count;
|
||||
laser_trap.last_step_count = step_events_completed;
|
||||
/*
|
||||
* Adjust Laser Power - Accelerating
|
||||
* isPowered - True when a move is powered.
|
||||
* isEnabled - laser power is active.
|
||||
* Laser power variables are calulated and stored in this block by the planner code.
|
||||
*
|
||||
* trap_ramp_active_pwr - the active power in this block across accel or decel trap steps.
|
||||
* trap_ramp_entry_incr - holds the precalculated value to increase the current power per accel step.
|
||||
*
|
||||
* Apply the starting active power and then increase power per step by the trap_ramp_entry_incr value if positive.
|
||||
*/
|
||||
|
||||
// Should be faster than a divide, since this should trip just once
|
||||
if (laser_trap.acc_step_count < 0) {
|
||||
while (laser_trap.acc_step_count < 0) {
|
||||
laser_trap.acc_step_count += current_block->laser.entry_per;
|
||||
if (laser_trap.cur_power < current_block->laser.power) laser_trap.cur_power++;
|
||||
}
|
||||
cutter.ocr_set_power(laser_trap.cur_power);
|
||||
}
|
||||
#if ENABLED(LASER_POWER_TRAP)
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
|
||||
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
|
||||
if (current_block->laser.trap_ramp_entry_incr > 0) {
|
||||
cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
|
||||
current_block->laser.trap_ramp_active_pwr += current_block->laser.trap_ramp_entry_incr;
|
||||
}
|
||||
#else
|
||||
if (laser_trap.till_update)
|
||||
laser_trap.till_update--;
|
||||
else {
|
||||
laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
|
||||
laser_trap.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate;
|
||||
cutter.ocr_set_power(laser_trap.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// Not a powered move.
|
||||
else cutter.apply_power(0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -2066,7 +2047,6 @@ uint32_t Stepper::block_phase_isr() {
|
||||
: current_block->final_rate;
|
||||
}
|
||||
#else
|
||||
|
||||
// Using the old trapezoidal control
|
||||
step_rate = STEP_MULTIPLY(deceleration_time, current_block->acceleration_rate);
|
||||
if (step_rate < acc_step_rate) { // Still decelerating?
|
||||
@ -2094,37 +2074,25 @@ uint32_t Stepper::block_phase_isr() {
|
||||
else if (LA_steps) nextAdvanceISR = 0;
|
||||
#endif // LIN_ADVANCE
|
||||
|
||||
// Update laser - Decelerating
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
if (laser_trap.enabled) {
|
||||
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
if (current_block->laser.exit_per) {
|
||||
laser_trap.acc_step_count -= step_events_completed - laser_trap.last_step_count;
|
||||
laser_trap.last_step_count = step_events_completed;
|
||||
|
||||
// Should be faster than a divide, since this should trip just once
|
||||
if (laser_trap.acc_step_count < 0) {
|
||||
while (laser_trap.acc_step_count < 0) {
|
||||
laser_trap.acc_step_count += current_block->laser.exit_per;
|
||||
if (laser_trap.cur_power > current_block->laser.power_exit) laser_trap.cur_power--;
|
||||
}
|
||||
cutter.ocr_set_power(laser_trap.cur_power);
|
||||
}
|
||||
/*
|
||||
* Adjust Laser Power - Decelerating
|
||||
* trap_ramp_entry_decr - holds the precalculated value to decrease the current power per decel step.
|
||||
*/
|
||||
#if ENABLED(LASER_POWER_TRAP)
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
|
||||
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
|
||||
if (current_block->laser.trap_ramp_exit_decr > 0) {
|
||||
current_block->laser.trap_ramp_active_pwr -= current_block->laser.trap_ramp_exit_decr;
|
||||
cutter.apply_power(current_block->laser.trap_ramp_active_pwr);
|
||||
}
|
||||
#else
|
||||
if (laser_trap.till_update)
|
||||
laser_trap.till_update--;
|
||||
else {
|
||||
laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
|
||||
laser_trap.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate;
|
||||
cutter.ocr_set_power(laser_trap.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles
|
||||
}
|
||||
#endif
|
||||
// Not a powered move.
|
||||
else cutter.apply_power(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
// Must be in cruise phase otherwise
|
||||
else {
|
||||
else { // Must be in cruise phase otherwise
|
||||
|
||||
#if ENABLED(LIN_ADVANCE)
|
||||
// If there are any esteps, fire the next advance_isr "now"
|
||||
@ -2139,24 +2107,50 @@ uint32_t Stepper::block_phase_isr() {
|
||||
|
||||
// The timer interval is just the nominal value for the nominal speed
|
||||
interval = ticks_nominal;
|
||||
|
||||
// Update laser - Cruising
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
if (laser_trap.enabled) {
|
||||
if (!laser_trap.cruise_set) {
|
||||
laser_trap.cur_power = current_block->laser.power;
|
||||
cutter.ocr_set_power(laser_trap.cur_power);
|
||||
laser_trap.cruise_set = true;
|
||||
}
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
laser_trap.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
|
||||
#else
|
||||
laser_trap.last_step_count = step_events_completed;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Adjust Laser Power - Cruise
|
||||
* power - direct or floor adjusted active laser power.
|
||||
*/
|
||||
|
||||
#if ENABLED(LASER_POWER_TRAP)
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
|
||||
if (step_events_completed + 1 == accelerate_until) {
|
||||
if (planner.laser_inline.status.isPowered && planner.laser_inline.status.isEnabled) {
|
||||
if (current_block->laser.trap_ramp_entry_incr > 0) {
|
||||
current_block->laser.trap_ramp_active_pwr = current_block->laser.power;
|
||||
cutter.apply_power(current_block->laser.power);
|
||||
}
|
||||
}
|
||||
// Not a powered move.
|
||||
else cutter.apply_power(0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
/*
|
||||
* CUTTER_MODE_DYNAMIC is experimental and developing.
|
||||
* Super-fast method to dynamically adjust the laser power OCR value based on the input feedrate in mm-per-minute.
|
||||
* TODO: Set up Min/Max OCR offsets to allow tuning and scaling of various lasers.
|
||||
* TODO: Integrate accel/decel +-rate into the dynamic laser power calc.
|
||||
*/
|
||||
if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC
|
||||
&& planner.laser_inline.status.isPowered // isPowered flag set on any parsed G1, G2, G3, or G5 move; cleared on any others.
|
||||
&& cutter.last_block_power != current_block->laser.power // Prevent constant update without change
|
||||
) {
|
||||
cutter.apply_power(current_block->laser.power);
|
||||
cutter.last_block_power = current_block->laser.power;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else { // !current_block
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC) {
|
||||
cutter.apply_power(0); // No movement in dynamic mode so turn Laser off
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// If there is no current block at this point, attempt to pop one from the buffer
|
||||
@ -2169,11 +2163,18 @@ uint32_t Stepper::block_phase_isr() {
|
||||
// Sync block? Sync the stepper counts or fan speeds and return
|
||||
while (current_block->is_sync()) {
|
||||
|
||||
if (current_block->is_fan_sync()) {
|
||||
TERN_(LASER_SYNCHRONOUS_M106_M107, planner.sync_fan_speeds(current_block->fan_speed));
|
||||
}
|
||||
else
|
||||
_set_position(current_block->position);
|
||||
#if ENABLED(LASER_POWER_SYNC)
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) {
|
||||
if (current_block->is_pwr_sync()) {
|
||||
planner.laser_inline.status.isSyncPower = true;
|
||||
cutter.apply_power(current_block->laser.power);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TERN_(LASER_SYNCHRONOUS_M106_M107, if (current_block->is_fan_sync()) planner.sync_fan_speeds(current_block->fan_speed));
|
||||
|
||||
if (!(current_block->is_fan_sync() || current_block->is_pwr_sync())) _set_position(current_block->position);
|
||||
|
||||
discard_current_block();
|
||||
|
||||
@ -2183,8 +2184,10 @@ uint32_t Stepper::block_phase_isr() {
|
||||
}
|
||||
|
||||
// For non-inline cutter, grossly apply power
|
||||
#if ENABLED(LASER_FEATURE) && DISABLED(LASER_POWER_INLINE)
|
||||
cutter.apply_power(current_block->cutter_power);
|
||||
#if HAS_CUTTER
|
||||
if (cutter.cutter_mode == CUTTER_MODE_STANDARD) {
|
||||
cutter.apply_power(current_block->cutter_power);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLED(POWER_LOSS_RECOVERY)
|
||||
@ -2357,36 +2360,22 @@ uint32_t Stepper::block_phase_isr() {
|
||||
set_directions(current_block->direction_bits);
|
||||
}
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE)
|
||||
const power_status_t stat = current_block->laser.status;
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
laser_trap.enabled = stat.isPlanned && stat.isEnabled;
|
||||
laser_trap.cur_power = current_block->laser.power_entry; // RESET STATE
|
||||
laser_trap.cruise_set = false;
|
||||
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
laser_trap.last_step_count = 0;
|
||||
laser_trap.acc_step_count = current_block->laser.entry_per / 2;
|
||||
#else
|
||||
laser_trap.till_update = 0;
|
||||
#endif
|
||||
// Always have PWM in this case
|
||||
if (stat.isPlanned) { // Planner controls the laser
|
||||
cutter.ocr_set_power(
|
||||
stat.isEnabled ? laser_trap.cur_power : 0 // ON with power or OFF
|
||||
);
|
||||
}
|
||||
#else
|
||||
if (stat.isPlanned) { // Planner controls the laser
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
cutter.ocr_set_power(
|
||||
stat.isEnabled ? current_block->laser.power : 0 // ON with power or OFF
|
||||
);
|
||||
#if ENABLED(LASER_FEATURE)
|
||||
if (cutter.cutter_mode == CUTTER_MODE_CONTINUOUS) { // Planner controls the laser
|
||||
if (planner.laser_inline.status.isSyncPower)
|
||||
// If the previous block was a M3 sync power then skip the trap power init otherwise it will 0 the sync power.
|
||||
planner.laser_inline.status.isSyncPower = false; // Clear the flag to process subsequent trap calc's.
|
||||
else if (current_block->laser.status.isEnabled) {
|
||||
#if ENABLED(LASER_POWER_TRAP)
|
||||
TERN_(DEBUG_LASER_TRAP, SERIAL_ECHO_MSG("InitTrapPwr:",current_block->laser.trap_ramp_active_pwr));
|
||||
cutter.apply_power(current_block->laser.status.isPowered ? current_block->laser.trap_ramp_active_pwr : 0);
|
||||
#else
|
||||
cutter.set_enabled(stat.isEnabled);
|
||||
TERN_(DEBUG_CUTTER_POWER, SERIAL_ECHO_MSG("InlinePwr:",current_block->laser.power));
|
||||
cutter.apply_power(current_block->laser.status.isPowered ? current_block->laser.power : 0);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#endif // LASER_POWER_INLINE
|
||||
}
|
||||
#endif // LASER_FEATURE
|
||||
|
||||
// If the endstop is already pressed, endstop interrupts won't invoke
|
||||
// endstop_triggered and the move will grind. So check here for a
|
||||
@ -2416,21 +2405,6 @@ uint32_t Stepper::block_phase_isr() {
|
||||
// Calculate the initial timer interval
|
||||
interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr);
|
||||
}
|
||||
#if ENABLED(LASER_POWER_INLINE_CONTINUOUS)
|
||||
else { // No new block found; so apply inline laser parameters
|
||||
// This should mean ending file with 'M5 I' will stop the laser; thus the inline flag isn't needed
|
||||
const power_status_t stat = planner.laser_inline.status;
|
||||
if (stat.isPlanned) { // Planner controls the laser
|
||||
#if ENABLED(SPINDLE_LASER_USE_PWM)
|
||||
cutter.ocr_set_power(
|
||||
stat.isEnabled ? planner.laser_inline.power : 0 // ON with power or OFF
|
||||
);
|
||||
#else
|
||||
cutter.set_enabled(stat.isEnabled);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return the interval to wait
|
||||
|
@ -444,25 +444,6 @@ class Stepper {
|
||||
// Current stepper motor directions (+1 or -1)
|
||||
static xyze_int8_t count_direction;
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||
|
||||
typedef struct {
|
||||
bool enabled; // Trapezoid needed flag (i.e., laser on, planner in control)
|
||||
uint8_t cur_power; // Current laser power
|
||||
bool cruise_set; // Power set up for cruising?
|
||||
|
||||
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||
uint16_t till_update; // Countdown to the next update
|
||||
#else
|
||||
uint32_t last_step_count, // Step count from the last update
|
||||
acc_step_count; // Bresenham counter for laser accel/decel
|
||||
#endif
|
||||
} stepper_laser_t;
|
||||
|
||||
static stepper_laser_t laser_trap;
|
||||
|
||||
#endif
|
||||
|
||||
public:
|
||||
// Initialize stepper hardware
|
||||
static void init();
|
||||
|
@ -1904,9 +1904,10 @@ void Temperature::task() {
|
||||
#if ENABLED(LASER_COOLANT_FLOW_METER)
|
||||
cooler.flowmeter_task(ms);
|
||||
#if ENABLED(FLOWMETER_SAFETY)
|
||||
if (cutter.enabled() && cooler.check_flow_too_low()) {
|
||||
if (cooler.check_flow_too_low()) {
|
||||
TERN_(HAS_DISPLAY, if (cutter.enabled()) ui.flow_fault());
|
||||
cutter.disable();
|
||||
TERN_(HAS_DISPLAY, ui.flow_fault());
|
||||
cutter.cutter_mode = CUTTER_MODE_ERROR; // Immediately kill stepper inline power output
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user