Skip to content

Commit

Permalink
⚡️ Fix and improve Inline Laser Power (MarlinFirmware#22690)
Browse files Browse the repository at this point in the history
  • Loading branch information
descipher authored and Omkar Dhekne committed Mar 25, 2024
1 parent 611050d commit 0b77b8d
Show file tree
Hide file tree
Showing 18 changed files with 831 additions and 694 deletions.
106 changes: 34 additions & 72 deletions Marlin/Configuration_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -3475,7 +3475,7 @@
// ESP32: If SPINDLE_LASER_PWM_PIN is onboard then <=78125Hz. For I2S expander
// the frequency determines the PWM resolution. 2500Hz = 0-100, 977Hz = 0-255, ...
// (250000 / SPINDLE_LASER_FREQUENCY) = max value.
#endif
#endif

//#define AIR_EVACUATION // Cutter Vacuum / Laser Blower motor control with G-codes M10-M11
#if ENABLED(AIR_EVACUATION)
Expand Down Expand Up @@ -3548,8 +3548,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
Expand All @@ -3562,79 +3565,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.
*
* 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.
*
* This allows the laser to keep in perfect sync with the planner and removes
* the powerup/down delay since lasers require negligible time.
* 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

/**
* 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
/**
* 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

#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)
Expand Down
88 changes: 51 additions & 37 deletions Marlin/src/feature/spindle_laser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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::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
#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)

/**
Expand All @@ -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
Expand All @@ -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);
}
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 0b77b8d

Please sign in to comment.