Various Laser / Spindle improvements (#15335)
This commit is contained in:
parent
e7e9304819
commit
df8b7dfc40
@ -2662,31 +2662,123 @@
|
|||||||
#define SPINDLE_LASER_ACTIVE_HIGH false // Set to "true" if the on/off function is active HIGH
|
#define SPINDLE_LASER_ACTIVE_HIGH false // Set to "true" if the on/off function is active HIGH
|
||||||
#define SPINDLE_LASER_PWM true // Set to "true" if your controller supports setting the speed/power
|
#define SPINDLE_LASER_PWM true // Set to "true" if your controller supports setting the speed/power
|
||||||
#define SPINDLE_LASER_PWM_INVERT true // Set to "true" if the speed/power goes up when you want it to go slower
|
#define SPINDLE_LASER_PWM_INVERT true // Set to "true" if the speed/power goes up when you want it to go slower
|
||||||
#define SPINDLE_LASER_POWERUP_DELAY 5000 // (ms) Delay to allow the spindle/laser to come up to speed/power
|
|
||||||
#define SPINDLE_LASER_POWERDOWN_DELAY 5000 // (ms) Delay to allow the spindle to stop
|
#define SPINDLE_LASER_FREQUENCY 2500 // (Hz) Spindle/laser frequency (only on supported HALs: AVR and LPC)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speed / Power can be set ('M3 S') and displayed in terms of:
|
||||||
|
* - PWM (S0 - S255)
|
||||||
|
* - PERCENT (S0 - S100)
|
||||||
|
* - RPM (S0 - S50000) Best for use with a spindle
|
||||||
|
*/
|
||||||
|
#define CUTTER_POWER_DISPLAY PWM
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relative mode uses relative range (SPEED_POWER_MIN to SPEED_POWER_MAX) instead of normal range (0 to SPEED_POWER_MAX)
|
||||||
|
* Best use with SuperPID router controller where for example S0 = 5,000 RPM and S255 = 30,000 RPM
|
||||||
|
*/
|
||||||
|
//#define CUTTER_POWER_RELATIVE // Set speed proportional to [SPEED_POWER_MIN...SPEED_POWER_MAX] instead of directly
|
||||||
|
|
||||||
#if ENABLED(SPINDLE_FEATURE)
|
#if ENABLED(SPINDLE_FEATURE)
|
||||||
//#define SPINDLE_CHANGE_DIR // Enable if your spindle controller can change spindle direction
|
//#define SPINDLE_CHANGE_DIR // Enable if your spindle controller can change spindle direction
|
||||||
#define SPINDLE_CHANGE_DIR_STOP // Enable if the spindle should stop before changing spin direction
|
#define SPINDLE_CHANGE_DIR_STOP // Enable if the spindle should stop before changing spin direction
|
||||||
#define SPINDLE_INVERT_DIR false // Set to "true" if the spin direction is reversed
|
#define SPINDLE_INVERT_DIR false // Set to "true" if the spin direction is reversed
|
||||||
|
|
||||||
|
#define SPINDLE_LASER_POWERUP_DELAY 5000 // (ms) Delay to allow the spindle/laser to come up to speed/power
|
||||||
|
#define SPINDLE_LASER_POWERDOWN_DELAY 5000 // (ms) Delay to allow the spindle to stop
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The M3 & M4 commands use the following equation to convert PWM duty cycle to speed/power
|
* M3/M4 uses the following equation to convert speed/power to PWM duty cycle
|
||||||
|
* Power = ((DC / 255 * 100) - SPEED_POWER_INTERCEPT)) * (1 / SPEED_POWER_SLOPE)
|
||||||
|
* where PWM DC varies from 0 to 255
|
||||||
*
|
*
|
||||||
* SPEED/POWER = PWM duty cycle * SPEED_POWER_SLOPE + SPEED_POWER_INTERCEPT
|
* Set these required parameters for your controller
|
||||||
* where PWM duty cycle varies from 0 to 255
|
|
||||||
*
|
|
||||||
* set the following for your controller (ALL MUST BE SET)
|
|
||||||
*/
|
*/
|
||||||
#define SPEED_POWER_SLOPE 118.4
|
#define SPEED_POWER_SLOPE 118.4 // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255
|
||||||
#define SPEED_POWER_INTERCEPT 0
|
#define SPEED_POWER_INTERCEPT 0
|
||||||
#define SPEED_POWER_MIN 5000
|
#define SPEED_POWER_MIN 5000
|
||||||
#define SPEED_POWER_MAX 30000 // SuperPID router controller 0 - 30,000 RPM
|
#define SPEED_POWER_MAX 30000 // SuperPID router controller 0 - 30,000 RPM
|
||||||
|
#define SPEED_POWER_STARTUP 25000 // The default value for speed power when M3 is called without arguments
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#define SPEED_POWER_SLOPE 0.3922
|
|
||||||
#define SPEED_POWER_INTERCEPT 0
|
#define SPEED_POWER_SLOPE 0.3922 // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255
|
||||||
#define SPEED_POWER_MIN 10
|
#define SPEED_POWER_INTERCEPT 0
|
||||||
#define SPEED_POWER_MAX 100 // 0-100%
|
#define SPEED_POWER_MIN 0
|
||||||
|
#define SPEED_POWER_MAX 100 // 0-100%
|
||||||
|
#define SPEED_POWER_STARTUP 80 // The default value for speed power when M3 is called without arguments
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*
|
||||||
|
* This allows the laser to keep in perfect sync with the planner and removes
|
||||||
|
* the powerup/down delay since lasers require negligible time.
|
||||||
|
*/
|
||||||
|
#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
|
||||||
|
#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
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -395,6 +395,8 @@ inline void HAL_adc_init() {
|
|||||||
// AVR compatibility
|
// AVR compatibility
|
||||||
#define strtof strtod
|
#define strtof strtod
|
||||||
|
|
||||||
|
#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set_pwm_frequency
|
* set_pwm_frequency
|
||||||
* Sets the frequency of the timer corresponding to the provided pin
|
* Sets the frequency of the timer corresponding to the provided pin
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
#include "../../inc/MarlinConfigPre.h"
|
#include "../../inc/MarlinConfigPre.h"
|
||||||
|
|
||||||
#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM
|
#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
|
||||||
|
|
||||||
#include "HAL.h"
|
#include "HAL.h"
|
||||||
|
|
||||||
@ -278,5 +278,5 @@ void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // FAST_PWM_FAN || SPINDLE_LASER_PWM
|
#endif // NEEDS_HARDWARE_PWM
|
||||||
#endif // __AVR__
|
#endif // __AVR__
|
||||||
|
@ -197,6 +197,8 @@ void HAL_idletask();
|
|||||||
#define PLATFORM_M997_SUPPORT
|
#define PLATFORM_M997_SUPPORT
|
||||||
void flashFirmware(const int16_t);
|
void flashFirmware(const int16_t);
|
||||||
|
|
||||||
|
#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set_pwm_frequency
|
* set_pwm_frequency
|
||||||
* Set the frequency of the timer corresponding to the provided pin
|
* Set the frequency of the timer corresponding to the provided pin
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
#include "../../inc/MarlinConfigPre.h"
|
#include "../../inc/MarlinConfigPre.h"
|
||||||
|
|
||||||
#if ENABLED(FAST_PWM_FAN) || SPINDLE_LASER_PWM
|
#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
|
||||||
|
|
||||||
#include <pwm.h>
|
#include <pwm.h>
|
||||||
|
|
||||||
|
@ -420,7 +420,11 @@ void startOrResumeJob() {
|
|||||||
#if DISABLED(SD_ABORT_NO_COOLDOWN)
|
#if DISABLED(SD_ABORT_NO_COOLDOWN)
|
||||||
thermalManager.disable_all_heaters();
|
thermalManager.disable_all_heaters();
|
||||||
#endif
|
#endif
|
||||||
thermalManager.zero_fan_speeds();
|
#if !HAS_CUTTER
|
||||||
|
thermalManager.zero_fan_speeds();
|
||||||
|
#else
|
||||||
|
cutter.kill(); // Full cutter shutdown including ISR control
|
||||||
|
#endif
|
||||||
wait_for_heatup = false;
|
wait_for_heatup = false;
|
||||||
#if ENABLED(POWER_LOSS_RECOVERY)
|
#if ENABLED(POWER_LOSS_RECOVERY)
|
||||||
recovery.purge();
|
recovery.purge();
|
||||||
@ -741,6 +745,10 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) {
|
|||||||
void kill(PGM_P const lcd_error/*=nullptr*/, PGM_P const lcd_component/*=nullptr*/, const bool steppers_off/*=false*/) {
|
void kill(PGM_P const lcd_error/*=nullptr*/, PGM_P const lcd_component/*=nullptr*/, const bool steppers_off/*=false*/) {
|
||||||
thermalManager.disable_all_heaters();
|
thermalManager.disable_all_heaters();
|
||||||
|
|
||||||
|
#if HAS_CUTTER
|
||||||
|
cutter.kill(); // Full cutter shutdown including ISR control
|
||||||
|
#endif
|
||||||
|
|
||||||
SERIAL_ERROR_MSG(STR_ERR_KILLED);
|
SERIAL_ERROR_MSG(STR_ERR_KILLED);
|
||||||
|
|
||||||
#if HAS_DISPLAY
|
#if HAS_DISPLAY
|
||||||
@ -770,6 +778,10 @@ void minkill(const bool steppers_off/*=false*/) {
|
|||||||
// Reiterate heaters off
|
// Reiterate heaters off
|
||||||
thermalManager.disable_all_heaters();
|
thermalManager.disable_all_heaters();
|
||||||
|
|
||||||
|
#if HAS_CUTTER
|
||||||
|
cutter.kill(); // Reiterate cutter shutdown
|
||||||
|
#endif
|
||||||
|
|
||||||
// Power off all steppers (for M112) or just the E steppers
|
// Power off all steppers (for M112) or just the E steppers
|
||||||
steppers_off ? disable_all_steppers() : disable_e_steppers();
|
steppers_off ? disable_all_steppers() : disable_e_steppers();
|
||||||
|
|
||||||
@ -789,14 +801,14 @@ void minkill(const bool steppers_off/*=false*/) {
|
|||||||
// Wait for kill to be pressed
|
// Wait for kill to be pressed
|
||||||
while (READ(KILL_PIN)) watchdog_refresh();
|
while (READ(KILL_PIN)) watchdog_refresh();
|
||||||
|
|
||||||
void (*resetFunc)() = 0; // Declare resetFunc() at address 0
|
void (*resetFunc)() = 0; // Declare resetFunc() at address 0
|
||||||
resetFunc(); // Jump to address 0
|
resetFunc(); // Jump to address 0
|
||||||
|
|
||||||
#else // !HAS_KILL
|
#else
|
||||||
|
|
||||||
for (;;) watchdog_refresh(); // Wait for reset
|
for (;;) watchdog_refresh(); // Wait for reset
|
||||||
|
|
||||||
#endif // !HAS_KILL
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,15 +174,6 @@
|
|||||||
// Defines that can't be evaluated now
|
// Defines that can't be evaluated now
|
||||||
#define HAS_TMC_SW_SERIAL ANY_AXIS_HAS(SW_SERIAL)
|
#define HAS_TMC_SW_SERIAL ANY_AXIS_HAS(SW_SERIAL)
|
||||||
|
|
||||||
//
|
|
||||||
// Stretching 'drivers.h' to include LPC/SAMD51 SD options
|
|
||||||
//
|
|
||||||
#define _SDCARD_LCD 1
|
|
||||||
#define _SDCARD_ONBOARD 2
|
|
||||||
#define _SDCARD_CUSTOM_CABLE 3
|
|
||||||
#define _SDCARD_ID(V) _CAT(_SDCARD_, V)
|
|
||||||
#define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V))
|
|
||||||
|
|
||||||
#if HAS_DRIVER(L6470) || HAS_DRIVER(L6474) || HAS_DRIVER(L6480) || HAS_DRIVER(POWERSTEP01)
|
#if HAS_DRIVER(L6470) || HAS_DRIVER(L6474) || HAS_DRIVER(L6480) || HAS_DRIVER(POWERSTEP01)
|
||||||
#define HAS_L64XX 1
|
#define HAS_L64XX 1
|
||||||
#endif
|
#endif
|
||||||
|
@ -32,10 +32,17 @@
|
|||||||
|
|
||||||
SpindleLaser cutter;
|
SpindleLaser cutter;
|
||||||
|
|
||||||
cutter_power_t SpindleLaser::power; // = 0
|
cutter_power_t SpindleLaser::power;
|
||||||
|
bool SpindleLaser::isOn; // state to determine when to apply setPower to power
|
||||||
|
cutter_setPower_t SpindleLaser::setPower = interpret_power(SPEED_POWER_MIN); // spindle/laser speed/power control in PWM, Percentage or RPM
|
||||||
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
|
cutter_frequency_t SpindleLaser::frequency; // setting PWM frequency; range: 2K - 50K
|
||||||
|
#endif
|
||||||
#define SPINDLE_LASER_PWM_OFF ((SPINDLE_LASER_PWM_INVERT) ? 255 : 0)
|
#define SPINDLE_LASER_PWM_OFF ((SPINDLE_LASER_PWM_INVERT) ? 255 : 0)
|
||||||
|
|
||||||
|
//
|
||||||
|
// Init the cutter to a safe OFF state
|
||||||
|
//
|
||||||
void SpindleLaser::init() {
|
void SpindleLaser::init() {
|
||||||
OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Init spindle to off
|
OUT_WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Init spindle to off
|
||||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||||
@ -45,41 +52,39 @@ void SpindleLaser::init() {
|
|||||||
SET_PWM(SPINDLE_LASER_PWM_PIN);
|
SET_PWM(SPINDLE_LASER_PWM_PIN);
|
||||||
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // set to lowest speed
|
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // set to lowest speed
|
||||||
#endif
|
#endif
|
||||||
|
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
|
||||||
|
set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_FREQUENCY);
|
||||||
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
|
frequency = SPINDLE_LASER_FREQUENCY;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ENABLED(SPINDLE_LASER_PWM)
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ocr_val_mode() is used for debugging and to get the points needed to compute the RPM vs ocr_val line
|
* Set the cutter PWM directly to the given ocr value
|
||||||
*
|
**/
|
||||||
* it accepts inputs of 0-255
|
|
||||||
*/
|
|
||||||
void SpindleLaser::set_ocr(const uint8_t ocr) {
|
void SpindleLaser::set_ocr(const uint8_t ocr) {
|
||||||
WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on (active low)
|
WRITE(SPINDLE_LASER_ENA_PIN, SPINDLE_LASER_ACTIVE_HIGH); // turn spindle on
|
||||||
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
|
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), ocr ^ SPINDLE_LASER_PWM_OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set cutter ON state (and PWM) to the given cutter power value
|
||||||
|
//
|
||||||
void SpindleLaser::apply_power(const cutter_power_t inpow) {
|
void SpindleLaser::apply_power(const cutter_power_t inpow) {
|
||||||
static cutter_power_t last_power_applied = 0;
|
static cutter_power_t last_power_applied = 0;
|
||||||
if (inpow == last_power_applied) return;
|
if (inpow == last_power_applied) return;
|
||||||
last_power_applied = inpow;
|
last_power_applied = inpow;
|
||||||
#if ENABLED(SPINDLE_LASER_PWM)
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
if (enabled()) {
|
if (enabled())
|
||||||
#define _scaled(F) ((F - (SPEED_POWER_INTERCEPT)) * inv_slope)
|
set_ocr(translate_power(inpow));
|
||||||
constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE),
|
|
||||||
min_ocr = _scaled(SPEED_POWER_MIN),
|
|
||||||
max_ocr = _scaled(SPEED_POWER_MAX);
|
|
||||||
int16_t ocr_val;
|
|
||||||
if (inpow <= SPEED_POWER_MIN) ocr_val = min_ocr; // Use minimum if set below
|
|
||||||
else if (inpow >= SPEED_POWER_MAX) ocr_val = max_ocr; // Use maximum if set above
|
|
||||||
else ocr_val = _scaled(inpow); // Use calculated OCR value
|
|
||||||
set_ocr(ocr_val & 0xFF); // ...limited to Atmel PWM max
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Turn spindle off (active low)
|
WRITE(SPINDLE_LASER_ENA_PIN, !SPINDLE_LASER_ACTIVE_HIGH); // Turn spindle off
|
||||||
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Only write low byte
|
analogWrite(pin_t(SPINDLE_LASER_PWM_PIN), SPINDLE_LASER_PWM_OFF); // Only write low byte
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
WRITE(SPINDLE_LASER_ENA_PIN, (SPINDLE_LASER_ACTIVE_HIGH) ? enabled() : !enabled());
|
WRITE(SPINDLE_LASER_ENA_PIN, (SPINDLE_LASER_ACTIVE_HIGH) ? enabled() : !enabled());
|
||||||
@ -88,6 +93,10 @@ void SpindleLaser::apply_power(const cutter_power_t inpow) {
|
|||||||
|
|
||||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set the spindle direction and apply immediately
|
||||||
|
// Stop on direction change if SPINDLE_STOP_ON_DIR_CHANGE is enabled
|
||||||
|
//
|
||||||
void SpindleLaser::set_direction(const bool reverse) {
|
void SpindleLaser::set_direction(const bool reverse) {
|
||||||
const bool dir_state = (reverse == SPINDLE_INVERT_DIR); // Forward (M3) HIGH when not inverted
|
const bool dir_state = (reverse == SPINDLE_INVERT_DIR); // Forward (M3) HIGH when not inverted
|
||||||
#if ENABLED(SPINDLE_STOP_ON_DIR_CHANGE)
|
#if ENABLED(SPINDLE_STOP_ON_DIR_CHANGE)
|
||||||
|
@ -28,55 +28,98 @@
|
|||||||
|
|
||||||
#include "../inc/MarlinConfig.h"
|
#include "../inc/MarlinConfig.h"
|
||||||
|
|
||||||
#if ENABLED(SPINDLE_FEATURE)
|
#include "spindle_laser_types.h"
|
||||||
#define _MSG_CUTTER(M) MSG_SPINDLE_##M
|
|
||||||
#else
|
|
||||||
#define _MSG_CUTTER(M) MSG_LASER_##M
|
|
||||||
#endif
|
|
||||||
#define MSG_CUTTER(M) _MSG_CUTTER(M)
|
|
||||||
|
|
||||||
#if SPEED_POWER_MAX > 255
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
typedef uint16_t cutter_power_t;
|
#include "../module/planner.h"
|
||||||
#define CUTTER_MENU_TYPE uint16_5
|
|
||||||
#else
|
|
||||||
typedef uint8_t cutter_power_t;
|
|
||||||
#define CUTTER_MENU_TYPE uint8
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class SpindleLaser {
|
class SpindleLaser {
|
||||||
public:
|
public:
|
||||||
|
static bool isOn; // state to determine when to apply setPower to power
|
||||||
static cutter_power_t power;
|
static cutter_power_t power;
|
||||||
static inline uint8_t powerPercent(const uint8_t pp) { return ui8_to_percent(pp); } // for display
|
static cutter_setPower_t setPower; // spindle/laser menu set power; in PWM, Percentage or RPM
|
||||||
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
|
static cutter_frequency_t frequency; // set PWM frequency; range: 2K-50K
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static cutter_setPower_t interpret_power(const float pwr) { // convert speed/power to configured PWM, Percentage or RPM in relative or normal range
|
||||||
|
#if CUTTER_DISPLAY_IS(PERCENT)
|
||||||
|
return (pwr / SPEED_POWER_MAX) * 100; // to percent
|
||||||
|
#elif CUTTER_DISPLAY_IS(RPM) // to RPM is unaltered
|
||||||
|
return pwr;
|
||||||
|
#else // to PWM
|
||||||
|
#if ENABLED(CUTTER_POWER_RELATIVE)
|
||||||
|
return (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 255; // using rpm range as relative percentage
|
||||||
|
#else
|
||||||
|
return (pwr / SPEED_POWER_MAX) * 255;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Translate speed/power --> percentage --> PWM value
|
||||||
|
**/
|
||||||
|
static cutter_power_t translate_power(const float pwr) {
|
||||||
|
float pwrpc;
|
||||||
|
#if CUTTER_DISPLAY_IS(PERCENT)
|
||||||
|
pwrpc = pwr;
|
||||||
|
#elif CUTTER_DISPLAY_IS(RPM) // RPM to percent
|
||||||
|
#if ENABLED(CUTTER_POWER_RELATIVE)
|
||||||
|
pwrpc = (pwr - SPEED_POWER_MIN) / (SPEED_POWER_MAX - SPEED_POWER_MIN) * 100;
|
||||||
|
#else
|
||||||
|
pwrpc = pwr / SPEED_POWER_MAX * 100;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
return pwr; // PWM
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(SPINDLE_FEATURE)
|
||||||
|
#if ENABLED(CUTTER_POWER_RELATIVE)
|
||||||
|
constexpr float spmin = 0;
|
||||||
|
#else
|
||||||
|
constexpr float spmin = SPEED_POWER_MIN / SPEED_POWER_MAX * 100; // convert to percentage
|
||||||
|
#endif
|
||||||
|
constexpr float spmax = 100;
|
||||||
|
#else
|
||||||
|
constexpr float spmin = SPEED_POWER_MIN;
|
||||||
|
constexpr float spmax = SPEED_POWER_MAX;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr float inv_slope = RECIPROCAL(SPEED_POWER_SLOPE),
|
||||||
|
min_ocr = (spmin - (SPEED_POWER_INTERCEPT)) * inv_slope, // Minimum allowed
|
||||||
|
max_ocr = (spmax - (SPEED_POWER_INTERCEPT)) * inv_slope; // Maximum allowed
|
||||||
|
float ocr_val;
|
||||||
|
if (pwrpc < spmin) ocr_val = min_ocr; // Use minimum if set below
|
||||||
|
else if (pwrpc > spmax) ocr_val = max_ocr; // Use maximum if set above
|
||||||
|
else ocr_val = (pwrpc - (SPEED_POWER_INTERCEPT)) * inv_slope; // Use calculated OCR value
|
||||||
|
return ocr_val; // ...limited to Atmel PWM max
|
||||||
|
}
|
||||||
|
|
||||||
static void init();
|
static void init();
|
||||||
|
|
||||||
static inline bool enabled() { return !!power; }
|
// Modifying this function should update everywhere
|
||||||
|
static inline bool enabled(const cutter_power_t pwr) { return pwr > 0; }
|
||||||
static inline void set_power(const cutter_power_t pwr) { power = pwr; }
|
static inline bool enabled() { return enabled(power); }
|
||||||
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
static inline void refresh() { apply_power(power); }
|
static inline void refresh_frequency() { set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); }
|
||||||
|
#endif
|
||||||
static inline void set_enabled(const bool enable) {
|
|
||||||
const bool was = enabled();
|
|
||||||
set_power(enable ? 255 : 0);
|
|
||||||
if (was != enable) power_delay();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void apply_power(const cutter_power_t inpow);
|
static void apply_power(const cutter_power_t inpow);
|
||||||
|
|
||||||
//static bool active() { return READ(SPINDLE_LASER_ENA_PIN) == SPINDLE_LASER_ACTIVE_HIGH; }
|
FORCE_INLINE static void refresh() { apply_power(power); }
|
||||||
|
FORCE_INLINE static void set_power(const cutter_power_t pwr) { power = pwr; refresh(); }
|
||||||
|
|
||||||
static void update_output();
|
static inline void set_enabled(const bool enable) { set_power(enable ? (power ?: interpret_power(SPEED_POWER_STARTUP)) : 0); }
|
||||||
|
|
||||||
#if ENABLED(SPINDLE_LASER_PWM)
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
static void set_ocr(const uint8_t ocr);
|
static void set_ocr(const uint8_t ocr);
|
||||||
static inline void set_ocr_power(const cutter_power_t pwr) { power = pwr; set_ocr(pwr); }
|
static inline void set_ocr_power(const uint8_t pwr) { power = pwr; set_ocr(pwr); }
|
||||||
|
// static uint8_t translate_power(const cutter_power_t pwr); // Used by update output for power->OCR translation
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Wait for spindle to spin up or spin down
|
// Wait for spindle to spin up or spin down
|
||||||
static inline void power_delay() {
|
static inline void power_delay(const bool on) {
|
||||||
#if SPINDLE_LASER_POWERUP_DELAY || SPINDLE_LASER_POWERDOWN_DELAY
|
#if DISABLED(LASER_POWER_INLINE)
|
||||||
safe_delay(enabled() ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
|
safe_delay(on ? SPINDLE_LASER_POWERUP_DELAY : SPINDLE_LASER_POWERDOWN_DELAY);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,10 +129,44 @@ public:
|
|||||||
static inline void set_direction(const bool) {}
|
static inline void set_direction(const bool) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline void disable() { set_enabled(false); }
|
static inline void disable() { isOn = false; set_enabled(false); }
|
||||||
static inline void enable_forward() { set_direction(false); set_enabled(true); }
|
#if HAS_LCD_MENU
|
||||||
static inline void enable_reverse() { set_direction(true); set_enabled(true); }
|
static inline void enable_forward() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(false); set_enabled(true); }
|
||||||
|
static inline void enable_reverse() { isOn = true; setPower ? (power = setPower) : (setPower = interpret_power(SPEED_POWER_STARTUP)); set_direction(true); set_enabled(true); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
// Force disengage planner power control
|
||||||
|
static inline void inline_disable() { planner.settings.laser.status = 0; planner.settings.laser.power = 0; isOn = false;}
|
||||||
|
|
||||||
|
// Inline modes of all other functions; all enable planner inline power control
|
||||||
|
static inline void inline_enabled(const bool enable) { enable ? inline_power(SPEED_POWER_STARTUP) : inline_ocr_power(0); }
|
||||||
|
|
||||||
|
static void inline_power(const cutter_power_t pwr) {
|
||||||
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
|
inline_ocr_power(translate_power(pwr));
|
||||||
|
#else
|
||||||
|
planner.settings.laser.status = enabled(pwr) ? 0x03 : 0x01;
|
||||||
|
planner.settings.laser.power = pwr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void inline_direction(const bool reverse) { UNUSED(reverse); } // TODO is this ever going to be needed
|
||||||
|
|
||||||
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
|
static inline void inline_ocr_power(const uint8_t pwr) {
|
||||||
|
planner.settings.laser.status = pwr ? 0x03 : 0x01;
|
||||||
|
planner.settings.laser.power = pwr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline void kill() {
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
inline_disable();
|
||||||
|
#endif
|
||||||
|
disable();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern SpindleLaser cutter;
|
extern SpindleLaser cutter;
|
||||||
|
50
Marlin/src/feature/spindle_laser_types.h
Normal file
50
Marlin/src/feature/spindle_laser_types.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Marlin 3D Printer Firmware
|
||||||
|
* Copyright (c) 2019 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||||
|
*
|
||||||
|
* Based on Sprinter and grbl.
|
||||||
|
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* feature/spindle_laser_types.h
|
||||||
|
* Support for Laser Power or Spindle Power & Direction
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "../inc/MarlinConfigPre.h"
|
||||||
|
|
||||||
|
#if ENABLED(SPINDLE_FEATURE)
|
||||||
|
#define _MSG_CUTTER(M) MSG_SPINDLE_##M
|
||||||
|
#else
|
||||||
|
#define _MSG_CUTTER(M) MSG_LASER_##M
|
||||||
|
#endif
|
||||||
|
#define MSG_CUTTER(M) _MSG_CUTTER(M)
|
||||||
|
#if CUTTER_DISPLAY_IS(RPM) && SPEED_POWER_MAX > 255
|
||||||
|
#define cutter_power_t uint16_t
|
||||||
|
#define cutter_setPower_t uint16_t
|
||||||
|
#define CUTTER_MENU_POWER_TYPE uint16_5
|
||||||
|
#else
|
||||||
|
#define cutter_power_t uint8_t
|
||||||
|
#define cutter_setPower_t uint8_t
|
||||||
|
#define CUTTER_MENU_POWER_TYPE uint8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
|
#define cutter_frequency_t uint16_t
|
||||||
|
#define CUTTER_MENU_FREQUENCY_TYPE uint16_5
|
||||||
|
#endif
|
@ -28,6 +28,12 @@
|
|||||||
#include "../../feature/spindle_laser.h"
|
#include "../../feature/spindle_laser.h"
|
||||||
#include "../../module/stepper.h"
|
#include "../../module/stepper.h"
|
||||||
|
|
||||||
|
inline cutter_power_t get_s_power() {
|
||||||
|
return cutter_power_t(
|
||||||
|
parser.intval('S', cutter.interpret_power(SPEED_POWER_STARTUP))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Laser:
|
* Laser:
|
||||||
*
|
*
|
||||||
@ -71,29 +77,52 @@
|
|||||||
*/
|
*/
|
||||||
void GcodeSuite::M3_M4(const bool is_M4) {
|
void GcodeSuite::M3_M4(const bool is_M4) {
|
||||||
|
|
||||||
#if ENABLED(SPINDLE_FEATURE)
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
planner.synchronize(); // Wait for movement to complete before changing power
|
if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
|
||||||
|
// Laser power in inline mode
|
||||||
|
cutter.inline_direction(is_M4); // Should always be unused
|
||||||
|
|
||||||
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
|
if (parser.seen('O'))
|
||||||
|
cutter.inline_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t)
|
||||||
|
else
|
||||||
|
cutter.inline_power(get_s_power());
|
||||||
|
#else
|
||||||
|
cutter.inline_enabled(true);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Non-inline, standard case
|
||||||
|
cutter.inline_disable(); // Prevent future blocks re-setting the power
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
planner.synchronize(); // Wait for previous movement commands (G0/G0/G2/G3) to complete before changing power
|
||||||
|
|
||||||
cutter.set_direction(is_M4);
|
cutter.set_direction(is_M4);
|
||||||
|
|
||||||
#if ENABLED(SPINDLE_LASER_PWM)
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
if (parser.seenval('O'))
|
if (parser.seenval('O'))
|
||||||
cutter.set_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t)
|
cutter.set_ocr_power(parser.value_byte()); // The OCR is a value from 0 to 255 (uint8_t)
|
||||||
else
|
else
|
||||||
cutter.set_power(parser.intval('S', 255));
|
cutter.set_power(get_s_power());
|
||||||
#else
|
#else
|
||||||
cutter.set_enabled(true);
|
cutter.set_enabled(true);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* M5 - Cutter OFF
|
* M5 - Cutter OFF (when moves are complete)
|
||||||
*/
|
*/
|
||||||
void GcodeSuite::M5() {
|
void GcodeSuite::M5() {
|
||||||
#if ENABLED(SPINDLE_FEATURE)
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
planner.synchronize();
|
if (parser.seen('I') == DISABLED(LASER_POWER_INLINE_INVERT)) {
|
||||||
|
cutter.inline_enabled(false); // Laser power in inline mode
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Non-inline, standard case
|
||||||
|
cutter.inline_disable(); // Prevent future blocks re-setting the power
|
||||||
#endif
|
#endif
|
||||||
|
planner.synchronize();
|
||||||
cutter.set_enabled(false);
|
cutter.set_enabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,10 @@ GcodeSuite gcode;
|
|||||||
#include "../feature/cancel_object.h"
|
#include "../feature/cancel_object.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(LASER_MOVE_POWER)
|
||||||
|
#include "../feature/spindle_laser.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "../MarlinCore.h" // for idle()
|
#include "../MarlinCore.h" // for idle()
|
||||||
|
|
||||||
millis_t GcodeSuite::previous_move_ms;
|
millis_t GcodeSuite::previous_move_ms;
|
||||||
@ -172,6 +176,18 @@ void GcodeSuite::get_destination_from_command() {
|
|||||||
#if BOTH(MIXING_EXTRUDER, DIRECT_MIXING_IN_G1)
|
#if BOTH(MIXING_EXTRUDER, DIRECT_MIXING_IN_G1)
|
||||||
M165();
|
M165();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(LASER_MOVE_POWER)
|
||||||
|
// Set the laser power in the planner to configure this move
|
||||||
|
if (parser.seen('S'))
|
||||||
|
cutter.inline_power(parser.value_int());
|
||||||
|
else {
|
||||||
|
#if ENABLED(LASER_MOVE_G0_OFF)
|
||||||
|
if (parser.codenum == 0) // G0
|
||||||
|
cutter.inline_enabled(false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +69,7 @@ void GcodeSuite::G0_G1(
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
get_destination_from_command(); // Process X Y Z E F parameters
|
get_destination_from_command(); // Get X Y Z E F (and set cutter power)
|
||||||
|
|
||||||
#ifdef G0_FEEDRATE
|
#ifdef G0_FEEDRATE
|
||||||
if (fast_move) {
|
if (fast_move) {
|
||||||
|
@ -283,7 +283,7 @@ void GcodeSuite::G2_G3(const bool clockwise) {
|
|||||||
relative_mode = true;
|
relative_mode = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
get_destination_from_command();
|
get_destination_from_command(); // Get X Y Z E F (and set cutter power)
|
||||||
|
|
||||||
#if ENABLED(SF_ARC_FIX)
|
#if ENABLED(SF_ARC_FIX)
|
||||||
relative_mode = relative_mode_backup;
|
relative_mode = relative_mode_backup;
|
||||||
|
@ -116,7 +116,23 @@
|
|||||||
#define Z_STEPPER_ALIGN_AMP 1.0
|
#define Z_STEPPER_ALIGN_AMP 1.0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define HAS_CUTTER EITHER(SPINDLE_FEATURE, LASER_FEATURE)
|
//
|
||||||
|
// Spindle/Laser power display types
|
||||||
|
// Defined here so sanity checks can use them
|
||||||
|
//
|
||||||
|
#if EITHER(SPINDLE_FEATURE, LASER_FEATURE)
|
||||||
|
#define HAS_CUTTER 1
|
||||||
|
#define _CUTTER_DISP_PWM 1
|
||||||
|
#define _CUTTER_DISP_PERCENT 2
|
||||||
|
#define _CUTTER_DISP_RPM 3
|
||||||
|
#define _CUTTER_DISP(V) _CAT(_CUTTER_DISP_, V)
|
||||||
|
#define CUTTER_DISPLAY_IS(V) (_CUTTER_DISP(CUTTER_POWER_DISPLAY) == _CUTTER_DISP(V))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Add features that need hardware PWM here
|
||||||
|
#if ANY(FAST_PWM_FAN, SPINDLE_LASER_PWM)
|
||||||
|
#define NEEDS_HARDWARE_PWM 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !defined(__AVR__) || !defined(USBCON)
|
#if !defined(__AVR__) || !defined(USBCON)
|
||||||
// Define constants and variables for buffering serial data.
|
// Define constants and variables for buffering serial data.
|
||||||
@ -290,3 +306,17 @@
|
|||||||
#define MAXIMUM_STEPPER_RATE 250000
|
#define MAXIMUM_STEPPER_RATE 250000
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
//
|
||||||
|
// SD Card connection methods
|
||||||
|
// Defined here so pins and sanity checks can use them
|
||||||
|
//
|
||||||
|
#if ENABLED(SDSUPPORT)
|
||||||
|
#define _SDCARD_LCD 1
|
||||||
|
#define _SDCARD_ONBOARD 2
|
||||||
|
#define _SDCARD_CUSTOM_CABLE 3
|
||||||
|
#define _SDCARD_ID(V) _CAT(_SDCARD_, V)
|
||||||
|
#define SD_CONNECTION_IS(V) (_SDCARD_ID(SDCARD_CONNECTION) == _SDCARD_ID(V))
|
||||||
|
#else
|
||||||
|
#define SD_CONNECTION_IS(...) 0
|
||||||
|
#endif
|
||||||
|
@ -324,17 +324,36 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Override the SD_DETECT_STATE set in Configuration_adv.h
|
* Override the SD_DETECT_STATE set in Configuration_adv.h
|
||||||
|
* and enable sharing of onboard SD host drives (all platforms but AGCM4)
|
||||||
*/
|
*/
|
||||||
#if ENABLED(SDSUPPORT)
|
#if ENABLED(SDSUPPORT)
|
||||||
#if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION))
|
|
||||||
#undef SD_DETECT_STATE
|
#if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4)
|
||||||
#if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
|
//
|
||||||
#define SD_DETECT_STATE HIGH
|
// The external SD card is not used. Hardware SPI is used to access the card.
|
||||||
|
// When sharing the SD card with a PC we want the menu options to
|
||||||
|
// mount/unmount the card and refresh it. So we disable card detect.
|
||||||
|
//
|
||||||
|
#undef SD_DETECT_PIN
|
||||||
|
#define SHARED_SD_CARD
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if DISABLED(SHARED_SD_CARD)
|
||||||
|
#define INIT_SDCARD_ON_BOOT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if PIN_EXISTS(SD_DETECT)
|
||||||
|
#if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION))
|
||||||
|
#undef SD_DETECT_STATE
|
||||||
|
#if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
|
||||||
|
#define SD_DETECT_STATE HIGH
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef SD_DETECT_STATE
|
||||||
|
#define SD_DETECT_STATE LOW
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#ifndef SD_DETECT_STATE
|
|
||||||
#define SD_DETECT_STATE LOW
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2153,21 +2172,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLED(SDSUPPORT)
|
|
||||||
#if SD_CONNECTION_IS(ONBOARD) && DISABLED(NO_SD_HOST_DRIVE) && !defined(ARDUINO_GRAND_CENTRAL_M4)
|
|
||||||
//
|
|
||||||
// The external SD card is not used. Hardware SPI is used to access the card.
|
|
||||||
// When sharing the SD card with a PC we want the menu options to
|
|
||||||
// mount/unmount the card and refresh it. So we disable card detect.
|
|
||||||
//
|
|
||||||
#undef SD_DETECT_PIN
|
|
||||||
#define SHARED_SD_CARD
|
|
||||||
#endif
|
|
||||||
#if DISABLED(SHARED_SD_CARD)
|
|
||||||
#define INIT_SDCARD_ON_BOOT 1
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !NUM_SERIAL
|
#if !NUM_SERIAL
|
||||||
#undef BAUD_RATE_GCODE
|
#undef BAUD_RATE_GCODE
|
||||||
#endif
|
#endif
|
||||||
|
@ -1451,7 +1451,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
|
|||||||
* Deploying the Allen Key probe uses big moves in z direction. Too dangerous for an unhomed z-axis.
|
* Deploying the Allen Key probe uses big moves in z direction. Too dangerous for an unhomed z-axis.
|
||||||
*/
|
*/
|
||||||
#if ENABLED(Z_PROBE_ALLEN_KEY) && (Z_HOME_DIR < 0) && ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
|
#if ENABLED(Z_PROBE_ALLEN_KEY) && (Z_HOME_DIR < 0) && ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
|
||||||
#error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY"
|
#error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2654,9 +2654,9 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
|
|||||||
|
|
||||||
#if ENABLED(BACKLASH_COMPENSATION)
|
#if ENABLED(BACKLASH_COMPENSATION)
|
||||||
#ifndef BACKLASH_DISTANCE_MM
|
#ifndef BACKLASH_DISTANCE_MM
|
||||||
#error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM"
|
#error "BACKLASH_COMPENSATION requires BACKLASH_DISTANCE_MM."
|
||||||
#elif !defined(BACKLASH_CORRECTION)
|
#elif !defined(BACKLASH_CORRECTION)
|
||||||
#error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION"
|
#error "BACKLASH_COMPENSATION requires BACKLASH_CORRECTION."
|
||||||
#elif IS_CORE
|
#elif IS_CORE
|
||||||
constexpr float backlash_arr[] = BACKLASH_DISTANCE_MM;
|
constexpr float backlash_arr[] = BACKLASH_DISTANCE_MM;
|
||||||
static_assert(!backlash_arr[CORE_AXIS_1] && !backlash_arr[CORE_AXIS_2],
|
static_assert(!backlash_arr[CORE_AXIS_1] && !backlash_arr[CORE_AXIS_2],
|
||||||
@ -2736,6 +2736,45 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if HAS_CUTTER
|
#if HAS_CUTTER
|
||||||
|
#ifndef CUTTER_POWER_DISPLAY
|
||||||
|
#error "CUTTER_POWER_DISPLAY is required with a spindle or laser. Please update your Configuration_adv.h."
|
||||||
|
#elif !CUTTER_DISPLAY_IS(PWM) && !CUTTER_DISPLAY_IS(PERCENT) && !CUTTER_DISPLAY_IS(RPM)
|
||||||
|
#error "CUTTER_POWER_DISPLAY must be PWM, PERCENT, or RPM. Please update your Configuration_adv.h."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
#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. Please update your Configuration_adv.h."
|
||||||
|
#endif
|
||||||
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||||
|
#if DISABLED(SPINDLE_LASER_PWM)
|
||||||
|
#error "LASER_POWER_INLINE_TRAPEZOID requires SPINDLE_LASER_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
|
||||||
|
#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
|
||||||
#define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN)
|
#define _PIN_CONFLICT(P) (PIN_EXISTS(P) && P##_PIN == SPINDLE_LASER_PWM_PIN)
|
||||||
#if BOTH(SPINDLE_FEATURE, LASER_FEATURE)
|
#if BOTH(SPINDLE_FEATURE, LASER_FEATURE)
|
||||||
#error "Enable only one of SPINDLE_FEATURE or LASER_FEATURE."
|
#error "Enable only one of SPINDLE_FEATURE or LASER_FEATURE."
|
||||||
@ -2748,13 +2787,9 @@ static_assert( _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
|
|||||||
#error "SPINDLE_LASER_PWM_PIN is required for SPINDLE_LASER_PWM."
|
#error "SPINDLE_LASER_PWM_PIN is required for SPINDLE_LASER_PWM."
|
||||||
#elif !PWM_PIN(SPINDLE_LASER_PWM_PIN)
|
#elif !PWM_PIN(SPINDLE_LASER_PWM_PIN)
|
||||||
#error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
|
#error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin."
|
||||||
#elif 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 !defined(SPINDLE_LASER_PWM_INVERT)
|
#elif !defined(SPINDLE_LASER_PWM_INVERT)
|
||||||
#error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE."
|
#error "SPINDLE_LASER_PWM_INVERT is required for (SPINDLE|LASER)_FEATURE."
|
||||||
#elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX)
|
#elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX) || !defined(SPEED_POWER_STARTUP)
|
||||||
#error "SPINDLE_LASER_PWM equation constant(s) missing."
|
#error "SPINDLE_LASER_PWM equation constant(s) missing."
|
||||||
#elif _PIN_CONFLICT(X_MIN)
|
#elif _PIN_CONFLICT(X_MIN)
|
||||||
#error "SPINDLE_LASER_PWM pin conflicts with X_MIN_PIN."
|
#error "SPINDLE_LASER_PWM pin conflicts with X_MIN_PIN."
|
||||||
|
@ -570,8 +570,12 @@ void MarlinUI::draw_status_screen() {
|
|||||||
// Laser / Spindle
|
// Laser / Spindle
|
||||||
#if DO_DRAW_CUTTER
|
#if DO_DRAW_CUTTER
|
||||||
if (cutter.power && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
|
if (cutter.power && PAGE_CONTAINS(STATUS_CUTTER_TEXT_Y - INFO_FONT_ASCENT, STATUS_CUTTER_TEXT_Y - 1)) {
|
||||||
lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.powerPercent(cutter.power)));
|
lcd_put_u8str(STATUS_CUTTER_TEXT_X, STATUS_CUTTER_TEXT_Y, i16tostr3rj(cutter.power));
|
||||||
lcd_put_wchar('%');
|
#if CUTTER_DISPLAY_IS(PERCENT)
|
||||||
|
lcd_put_wchar('%');
|
||||||
|
#elif CUTTER_DISPLAY_IS(RPM)
|
||||||
|
lcd_put_wchar('K');
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -90,6 +90,7 @@ namespace Language_en {
|
|||||||
PROGMEM Language_Str MSG_PREHEAT_2_SETTINGS = _UxGT("Preheat ") PREHEAT_2_LABEL _UxGT(" Conf");
|
PROGMEM Language_Str MSG_PREHEAT_2_SETTINGS = _UxGT("Preheat ") PREHEAT_2_LABEL _UxGT(" Conf");
|
||||||
PROGMEM Language_Str MSG_PREHEAT_CUSTOM = _UxGT("Preheat Custom");
|
PROGMEM Language_Str MSG_PREHEAT_CUSTOM = _UxGT("Preheat Custom");
|
||||||
PROGMEM Language_Str MSG_COOLDOWN = _UxGT("Cooldown");
|
PROGMEM Language_Str MSG_COOLDOWN = _UxGT("Cooldown");
|
||||||
|
PROGMEM Language_Str MSG_CUTTER_FREQUENCY = _UxGT("Frequency");
|
||||||
PROGMEM Language_Str MSG_LASER_MENU = _UxGT("Laser Control");
|
PROGMEM Language_Str MSG_LASER_MENU = _UxGT("Laser Control");
|
||||||
PROGMEM Language_Str MSG_LASER_OFF = _UxGT("Laser Off");
|
PROGMEM Language_Str MSG_LASER_OFF = _UxGT("Laser Off");
|
||||||
PROGMEM Language_Str MSG_LASER_ON = _UxGT("Laser On");
|
PROGMEM Language_Str MSG_LASER_ON = _UxGT("Laser On");
|
||||||
|
@ -36,18 +36,29 @@
|
|||||||
|
|
||||||
START_MENU();
|
START_MENU();
|
||||||
BACK_ITEM(MSG_MAIN);
|
BACK_ITEM(MSG_MAIN);
|
||||||
if (cutter.enabled()) {
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
#if ENABLED(SPINDLE_LASER_PWM)
|
EDIT_ITEM_FAST(CUTTER_MENU_POWER_TYPE, MSG_CUTTER(POWER), &cutter.setPower, cutter.interpret_power(SPEED_POWER_MIN), cutter.interpret_power(SPEED_POWER_MAX),
|
||||||
EDIT_ITEM(CUTTER_MENU_TYPE, MSG_CUTTER(POWER), &cutter.power, SPEED_POWER_MIN, SPEED_POWER_MAX);
|
[]{
|
||||||
#endif
|
if (cutter.isOn) {
|
||||||
|
cutter.power = cutter.setPower;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (cutter.enabled() && cutter.isOn)
|
||||||
ACTION_ITEM(MSG_CUTTER(OFF), cutter.disable);
|
ACTION_ITEM(MSG_CUTTER(OFF), cutter.disable);
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
ACTION_ITEM(MSG_CUTTER(ON), cutter.enable_forward);
|
ACTION_ITEM(MSG_CUTTER(ON), cutter.enable_forward);
|
||||||
#if ENABLED(SPINDLE_CHANGE_DIR)
|
#if ENABLED(SPINDLE_CHANGE_DIR)
|
||||||
ACTION_ITEM(MSG_SPINDLE_REVERSE, cutter.enable_reverse);
|
ACTION_ITEM(MSG_SPINDLE_REVERSE, cutter.enable_reverse);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLED(MARLIN_DEV_MODE)
|
||||||
|
#if ENABLED(HAL_CAN_SET_PWM_FREQ) && defined(SPINDLE_LASER_FREQUENCY)
|
||||||
|
EDIT_ITEM_FAST(CUTTER_MENU_FREQUENCY_TYPE, MSG_CUTTER_FREQUENCY, &cutter.frequency, 2000, 50000,[]{ cutter.refresh_frequency();});
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
END_MENU();
|
END_MENU();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -815,11 +815,10 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
|
|||||||
#if ENABLED(S_CURVE_ACCELERATION)
|
#if ENABLED(S_CURVE_ACCELERATION)
|
||||||
// Jerk controlled speed requires to express speed versus time, NOT steps
|
// Jerk controlled speed requires to express speed versus time, NOT steps
|
||||||
uint32_t acceleration_time = ((float)(cruise_rate - initial_rate) / accel) * (STEPPER_TIMER_RATE),
|
uint32_t acceleration_time = ((float)(cruise_rate - initial_rate) / accel) * (STEPPER_TIMER_RATE),
|
||||||
deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE);
|
deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE),
|
||||||
|
|
||||||
// And to offload calculations from the ISR, we also calculate the inverse of those times here
|
// And to offload calculations from the ISR, we also calculate the inverse of those times here
|
||||||
uint32_t acceleration_time_inverse = get_period_inverse(acceleration_time);
|
acceleration_time_inverse = get_period_inverse(acceleration_time),
|
||||||
uint32_t deceleration_time_inverse = get_period_inverse(deceleration_time);
|
deceleration_time_inverse = get_period_inverse(deceleration_time);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Store new block parameters
|
// Store new block parameters
|
||||||
@ -834,6 +833,47 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
|
|||||||
block->cruise_rate = cruise_rate;
|
block->cruise_rate = cruise_rate;
|
||||||
#endif
|
#endif
|
||||||
block->final_rate = final_rate;
|
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;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
block->laser.entry_per = 0;
|
||||||
|
block->laser.power_entry = block->laser.power;
|
||||||
|
}
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
/* PLANNER SPEED DEFINITION
|
/* PLANNER SPEED DEFINITION
|
||||||
@ -1813,6 +1853,12 @@ bool Planner::_populate_block(block_t * const block, bool split_move,
|
|||||||
// Set direction bits
|
// Set direction bits
|
||||||
block->direction_bits = dm;
|
block->direction_bits = dm;
|
||||||
|
|
||||||
|
// Update block laser power
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
block->laser.status = settings.laser.status;
|
||||||
|
block->laser.power = settings.laser.power;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Number of steps for each axis
|
// Number of steps for each axis
|
||||||
// See http://www.corexy.com/theory.html
|
// See http://www.corexy.com/theory.html
|
||||||
#if CORE_IS_XY
|
#if CORE_IS_XY
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if HAS_CUTTER
|
#if HAS_CUTTER
|
||||||
#include "../feature/spindle_laser.h"
|
#include "../feature/spindle_laser_types.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Feedrate for manual moves
|
// Feedrate for manual moves
|
||||||
@ -88,6 +88,23 @@ enum BlockFlag : char {
|
|||||||
BLOCK_FLAG_SYNC_POSITION = _BV(BLOCK_BIT_SYNC_POSITION)
|
BLOCK_FLAG_SYNC_POSITION = _BV(BLOCK_BIT_SYNC_POSITION)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t status, // See planner settings for meaning
|
||||||
|
power; // Ditto; When in trapezoid mode this is nominal power
|
||||||
|
#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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct block_t
|
* struct block_t
|
||||||
*
|
*
|
||||||
@ -174,12 +191,36 @@ typedef struct block_t {
|
|||||||
uint32_t sdpos;
|
uint32_t sdpos;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
block_laser_t laser;
|
||||||
|
#endif
|
||||||
|
|
||||||
} block_t;
|
} block_t;
|
||||||
|
|
||||||
#define HAS_POSITION_FLOAT ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL)
|
#define HAS_POSITION_FLOAT ANY(LIN_ADVANCE, SCARA_FEEDRATE_SCALING, GRADIENT_MIX, LCD_SHOW_E_TOTAL)
|
||||||
|
|
||||||
#define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
|
#define BLOCK_MOD(n) ((n)&(BLOCK_BUFFER_SIZE-1))
|
||||||
|
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
typedef struct {
|
||||||
|
/**
|
||||||
|
* Laser status bitmask; most bits are unused;
|
||||||
|
* 0: Planner buffer enable
|
||||||
|
* 1: Laser enable
|
||||||
|
* 2: Reserved for direction
|
||||||
|
*/
|
||||||
|
uint8_t status;
|
||||||
|
/**
|
||||||
|
* Laser power: 0 or 255 in case of PWM-less laser,
|
||||||
|
* or the OCR value;
|
||||||
|
*
|
||||||
|
* Using OCR instead of raw power,
|
||||||
|
* as it avoids floating points during move loop
|
||||||
|
*/
|
||||||
|
uint8_t power;
|
||||||
|
} settings_laser_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t max_acceleration_mm_per_s2[XYZE_N], // (mm/s^2) M201 XYZE
|
uint32_t max_acceleration_mm_per_s2[XYZE_N], // (mm/s^2) M201 XYZE
|
||||||
min_segment_time_us; // (µs) M205 B
|
min_segment_time_us; // (µs) M205 B
|
||||||
@ -190,6 +231,9 @@ typedef struct {
|
|||||||
travel_acceleration; // (mm/s^2) M204 T - Travel acceleration. DEFAULT ACCELERATION for all NON printing moves.
|
travel_acceleration; // (mm/s^2) M204 T - Travel acceleration. DEFAULT ACCELERATION for all NON printing moves.
|
||||||
feedRate_t min_feedrate_mm_s, // (mm/s) M205 S - Minimum linear feedrate
|
feedRate_t min_feedrate_mm_s, // (mm/s) M205 S - Minimum linear feedrate
|
||||||
min_travel_feedrate_mm_s; // (mm/s) M205 T - Minimum travel feedrate
|
min_travel_feedrate_mm_s; // (mm/s) M205 T - Minimum travel feedrate
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
settings_laser_t laser;
|
||||||
|
#endif
|
||||||
} planner_settings_t;
|
} planner_settings_t;
|
||||||
|
|
||||||
#if DISABLED(SKEW_CORRECTION)
|
#if DISABLED(SKEW_CORRECTION)
|
||||||
|
@ -133,6 +133,10 @@ Stepper stepper; // Singleton
|
|||||||
#include "../feature/powerloss.h"
|
#include "../feature/powerloss.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if HAS_CUTTER
|
||||||
|
#include "../feature/spindle_laser.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
// public:
|
// public:
|
||||||
|
|
||||||
#if HAS_EXTRA_ENDSTOPS || ENABLED(Z_STEPPER_AUTO_ALIGN)
|
#if HAS_EXTRA_ENDSTOPS || ENABLED(Z_STEPPER_AUTO_ALIGN)
|
||||||
@ -236,6 +240,20 @@ xyz_long_t Stepper::endstops_trigsteps;
|
|||||||
xyze_long_t Stepper::count_position{0};
|
xyze_long_t Stepper::count_position{0};
|
||||||
xyze_int8_t Stepper::count_direction{0};
|
xyze_int8_t Stepper::count_direction{0};
|
||||||
|
|
||||||
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||||
|
Stepper::stepper_laser_t Stepper::laser = {
|
||||||
|
.trap_en = 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 DUAL_ENDSTOP_APPLY_STEP(A,V) \
|
#define DUAL_ENDSTOP_APPLY_STEP(A,V) \
|
||||||
if (separate_multi_axis) { \
|
if (separate_multi_axis) { \
|
||||||
if (A##_HOME_DIR < 0) { \
|
if (A##_HOME_DIR < 0) { \
|
||||||
@ -1674,10 +1692,9 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
|
|
||||||
#if ENABLED(S_CURVE_ACCELERATION)
|
#if ENABLED(S_CURVE_ACCELERATION)
|
||||||
// Get the next speed to use (Jerk limited!)
|
// Get the next speed to use (Jerk limited!)
|
||||||
uint32_t acc_step_rate =
|
uint32_t acc_step_rate = acceleration_time < current_block->acceleration_time
|
||||||
acceleration_time < current_block->acceleration_time
|
? _eval_bezier_curve(acceleration_time)
|
||||||
? _eval_bezier_curve(acceleration_time)
|
: current_block->cruise_rate;
|
||||||
: current_block->cruise_rate;
|
|
||||||
#else
|
#else
|
||||||
acc_step_rate = STEP_MULTIPLY(acceleration_time, current_block->acceleration_rate) + current_block->initial_rate;
|
acc_step_rate = STEP_MULTIPLY(acceleration_time, current_block->acceleration_rate) + current_block->initial_rate;
|
||||||
NOMORE(acc_step_rate, current_block->nominal_rate);
|
NOMORE(acc_step_rate, current_block->nominal_rate);
|
||||||
@ -1690,9 +1707,40 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
acceleration_time += interval;
|
acceleration_time += interval;
|
||||||
|
|
||||||
#if ENABLED(LIN_ADVANCE)
|
#if ENABLED(LIN_ADVANCE)
|
||||||
// Fire ISR if final adv_rate is reached
|
if (LA_use_advance_lead) {
|
||||||
if (LA_steps && (!LA_use_advance_lead || LA_isr_rate != current_block->advance_speed))
|
// Fire ISR if final adv_rate is reached
|
||||||
initiateLA();
|
if (LA_steps && LA_isr_rate != current_block->advance_speed) nextAdvanceISR = 0;
|
||||||
|
}
|
||||||
|
else if (LA_steps) nextAdvanceISR = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Update laser - Accelerating
|
||||||
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||||
|
if (laser.trap_en) {
|
||||||
|
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||||
|
if (current_block->laser.entry_per) {
|
||||||
|
laser.acc_step_count -= step_events_completed - laser.last_step_count;
|
||||||
|
laser.last_step_count = step_events_completed;
|
||||||
|
|
||||||
|
// Should be faster than a divide, since this should trip just once
|
||||||
|
if (laser.acc_step_count < 0) {
|
||||||
|
while (laser.acc_step_count < 0) {
|
||||||
|
laser.acc_step_count += current_block->laser.entry_per;
|
||||||
|
if (laser.cur_power < current_block->laser.power) laser.cur_power++;
|
||||||
|
}
|
||||||
|
cutter.set_ocr_power(laser.cur_power);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (laser.till_update)
|
||||||
|
laser.till_update--;
|
||||||
|
else {
|
||||||
|
laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
|
||||||
|
laser.cur_power = (current_block->laser.power * acc_step_rate) / current_block->nominal_rate;
|
||||||
|
cutter.set_ocr_power(laser.cur_power); // Cycle efficiency is irrelevant it the last line was many cycles
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// Are we in Deceleration phase ?
|
// Are we in Deceleration phase ?
|
||||||
@ -1740,10 +1788,39 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
LA_isr_rate = current_block->advance_speed;
|
LA_isr_rate = current_block->advance_speed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (LA_steps) initiateLA();
|
else if (LA_steps) nextAdvanceISR = 0;
|
||||||
|
#endif // LIN_ADVANCE
|
||||||
|
|
||||||
|
// Update laser - Decelerating
|
||||||
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||||
|
if (laser.trap_en) {
|
||||||
|
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||||
|
if (current_block->laser.exit_per) {
|
||||||
|
laser.acc_step_count -= step_events_completed - laser.last_step_count;
|
||||||
|
laser.last_step_count = step_events_completed;
|
||||||
|
|
||||||
|
// Should be faster than a divide, since this should trip just once
|
||||||
|
if (laser.acc_step_count < 0) {
|
||||||
|
while (laser.acc_step_count < 0) {
|
||||||
|
laser.acc_step_count += current_block->laser.exit_per;
|
||||||
|
if (laser.cur_power > current_block->laser.power_exit) laser.cur_power--;
|
||||||
|
}
|
||||||
|
cutter.set_ocr_power(laser.cur_power);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (laser.till_update)
|
||||||
|
laser.till_update--;
|
||||||
|
else {
|
||||||
|
laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
|
||||||
|
laser.cur_power = (current_block->laser.power * step_rate) / current_block->nominal_rate;
|
||||||
|
cutter.set_ocr_power(laser.cur_power); // Cycle efficiency isn't relevant when the last line was many cycles
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
// We must be in cruise phase otherwise
|
// Must be in cruise phase otherwise
|
||||||
else {
|
else {
|
||||||
|
|
||||||
#if ENABLED(LIN_ADVANCE)
|
#if ENABLED(LIN_ADVANCE)
|
||||||
@ -1759,6 +1836,22 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
|
|
||||||
// The timer interval is just the nominal value for the nominal speed
|
// The timer interval is just the nominal value for the nominal speed
|
||||||
interval = ticks_nominal;
|
interval = ticks_nominal;
|
||||||
|
|
||||||
|
// Update laser - Cruising
|
||||||
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||||
|
if (laser.trap_en) {
|
||||||
|
if (!laser.cruise_set) {
|
||||||
|
laser.cur_power = current_block->laser.power;
|
||||||
|
cutter.set_ocr_power(laser.cur_power);
|
||||||
|
laser.cruise_set = true;
|
||||||
|
}
|
||||||
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||||
|
laser.till_update = LASER_POWER_INLINE_TRAPEZOID_CONT_PER;
|
||||||
|
#else
|
||||||
|
laser.last_step_count = step_events_completed;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1805,11 +1898,11 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
* If DeltaA == DeltaB, the movement is only in the 1st axis (X)
|
* If DeltaA == DeltaB, the movement is only in the 1st axis (X)
|
||||||
*/
|
*/
|
||||||
#if EITHER(COREXY, COREXZ)
|
#if EITHER(COREXY, COREXZ)
|
||||||
#define X_CMP ==
|
#define X_CMP(A,B) ((A)==(B))
|
||||||
#else
|
#else
|
||||||
#define X_CMP !=
|
#define X_CMP(A,B) ((A)!=(B))
|
||||||
#endif
|
#endif
|
||||||
#define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) X_CMP D_(2)) )
|
#define X_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && X_CMP(D_(1),D_(2))) )
|
||||||
#else
|
#else
|
||||||
#define X_MOVE_TEST !!current_block->steps.a
|
#define X_MOVE_TEST !!current_block->steps.a
|
||||||
#endif
|
#endif
|
||||||
@ -1823,11 +1916,11 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
* If DeltaA == -DeltaB, the movement is only in the 2nd axis (Y or Z)
|
* If DeltaA == -DeltaB, the movement is only in the 2nd axis (Y or Z)
|
||||||
*/
|
*/
|
||||||
#if EITHER(COREYX, COREYZ)
|
#if EITHER(COREYX, COREYZ)
|
||||||
#define Y_CMP ==
|
#define Y_CMP(A,B) ((A)==(B))
|
||||||
#else
|
#else
|
||||||
#define Y_CMP !=
|
#define Y_CMP(A,B) ((A)!=(B))
|
||||||
#endif
|
#endif
|
||||||
#define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Y_CMP D_(2)) )
|
#define Y_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Y_CMP(D_(1),D_(2))) )
|
||||||
#else
|
#else
|
||||||
#define Y_MOVE_TEST !!current_block->steps.b
|
#define Y_MOVE_TEST !!current_block->steps.b
|
||||||
#endif
|
#endif
|
||||||
@ -1841,11 +1934,11 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
* If DeltaA == -DeltaB, the movement is only in the 2nd axis (Z)
|
* If DeltaA == -DeltaB, the movement is only in the 2nd axis (Z)
|
||||||
*/
|
*/
|
||||||
#if EITHER(COREZX, COREZY)
|
#if EITHER(COREZX, COREZY)
|
||||||
#define Z_CMP ==
|
#define Z_CMP(A,B) ((A)==(B))
|
||||||
#else
|
#else
|
||||||
#define Z_CMP !=
|
#define Z_CMP(A,B) ((A)!=(B))
|
||||||
#endif
|
#endif
|
||||||
#define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && D_(1) Z_CMP D_(2)) )
|
#define Z_MOVE_TEST ( S_(1) != S_(2) || (S_(1) > 0 && Z_CMP(D_(1),D_(2))) )
|
||||||
#else
|
#else
|
||||||
#define Z_MOVE_TEST !!current_block->steps.c
|
#define Z_MOVE_TEST !!current_block->steps.c
|
||||||
#endif
|
#endif
|
||||||
@ -1938,6 +2031,39 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
set_directions();
|
set_directions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLED(LASER_POWER_INLINE)
|
||||||
|
const uint8_t stat = current_block->laser.status;
|
||||||
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||||
|
laser.trap_en = (stat & 0x03) == 0x03;
|
||||||
|
laser.cur_power = current_block->laser.power_entry; // RESET STATE
|
||||||
|
laser.cruise_set = false;
|
||||||
|
#if DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||||
|
laser.last_step_count = 0;
|
||||||
|
laser.acc_step_count = current_block->laser.entry_per / 2;
|
||||||
|
#else
|
||||||
|
laser.till_update = 0;
|
||||||
|
#endif
|
||||||
|
// Always have PWM in this case
|
||||||
|
if (TEST(stat, 0)) { // Planner controls the laser
|
||||||
|
if (TEST(stat, 1)) // Laser is on
|
||||||
|
cutter.set_ocr_power(laser.cur_power);
|
||||||
|
else
|
||||||
|
cutter.set_power(0);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (TEST(stat, 0)) { // Planner controls the laser
|
||||||
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
|
if (TEST(stat, 1)) // Laser is on
|
||||||
|
cutter.set_ocr_power(current_block->laser.power);
|
||||||
|
else
|
||||||
|
cutter.set_power(0);
|
||||||
|
#else
|
||||||
|
cutter.set_enabled(TEST(stat, 1));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif // LASER_POWER_INLINE
|
||||||
|
|
||||||
// At this point, we must ensure the movement about to execute isn't
|
// At this point, we must ensure the movement about to execute isn't
|
||||||
// trying to force the head against a limit switch. If using interrupt-
|
// trying to force the head against a limit switch. If using interrupt-
|
||||||
// driven change detection, and already against a limit then no call to
|
// driven change detection, and already against a limit then no call to
|
||||||
@ -1957,21 +2083,35 @@ uint32_t Stepper::block_phase_isr() {
|
|||||||
// Mark the time_nominal as not calculated yet
|
// Mark the time_nominal as not calculated yet
|
||||||
ticks_nominal = -1;
|
ticks_nominal = -1;
|
||||||
|
|
||||||
#if DISABLED(S_CURVE_ACCELERATION)
|
|
||||||
// Set as deceleration point the initial rate of the block
|
|
||||||
acc_step_rate = current_block->initial_rate;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ENABLED(S_CURVE_ACCELERATION)
|
#if ENABLED(S_CURVE_ACCELERATION)
|
||||||
// Initialize the Bézier speed curve
|
// Initialize the Bézier speed curve
|
||||||
_calc_bezier_curve_coeffs(current_block->initial_rate, current_block->cruise_rate, current_block->acceleration_time_inverse);
|
_calc_bezier_curve_coeffs(current_block->initial_rate, current_block->cruise_rate, current_block->acceleration_time_inverse);
|
||||||
// We haven't started the 2nd half of the trapezoid
|
// We haven't started the 2nd half of the trapezoid
|
||||||
bezier_2nd_half = false;
|
bezier_2nd_half = false;
|
||||||
|
#else
|
||||||
|
// Set as deceleration point the initial rate of the block
|
||||||
|
acc_step_rate = current_block->initial_rate;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Calculate the initial timer interval
|
// Calculate the initial timer interval
|
||||||
interval = calc_timer_interval(current_block->initial_rate, &steps_per_isr);
|
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 uint8_t stat = planner.settings.laser.status;
|
||||||
|
if (TEST(stat, 0)) { // Planner controls the laser
|
||||||
|
#if ENABLED(SPINDLE_LASER_PWM)
|
||||||
|
if (TEST(stat, 1)) // Laser is on
|
||||||
|
cutter.set_ocr_power(planner.settings.laser.power);
|
||||||
|
else
|
||||||
|
cutter.set_power(0);
|
||||||
|
#else
|
||||||
|
cutter.set_enabled(TEST(stat, 1));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the interval to wait
|
// Return the interval to wait
|
||||||
|
@ -339,23 +339,35 @@ class Stepper {
|
|||||||
static uint32_t acc_step_rate; // needed for deceleration start point
|
static uint32_t acc_step_rate; // needed for deceleration start point
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//
|
|
||||||
// Exact steps at which an endstop was triggered
|
// Exact steps at which an endstop was triggered
|
||||||
//
|
|
||||||
static xyz_long_t endstops_trigsteps;
|
static xyz_long_t endstops_trigsteps;
|
||||||
|
|
||||||
//
|
|
||||||
// Positions of stepper motors, in step units
|
// Positions of stepper motors, in step units
|
||||||
//
|
|
||||||
static xyze_long_t count_position;
|
static xyze_long_t count_position;
|
||||||
|
|
||||||
//
|
// Current stepper motor directions (+1 or -1)
|
||||||
// Current direction of stepper motors (+1 or -1)
|
|
||||||
//
|
|
||||||
static xyze_int8_t count_direction;
|
static xyze_int8_t count_direction;
|
||||||
|
|
||||||
public:
|
#if ENABLED(LASER_POWER_INLINE_TRAPEZOID)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool trap_en; // 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 DISABLED(LASER_POWER_INLINE_TRAPEZOID_CONT)
|
||||||
|
uint32_t last_step_count, // Step count from the last update
|
||||||
|
acc_step_count; // Bresenham counter for laser accel/decel
|
||||||
|
#else
|
||||||
|
uint16_t till_update; // Countdown to the next update
|
||||||
|
#endif
|
||||||
|
} stepper_laser_t;
|
||||||
|
|
||||||
|
static stepper_laser_t laser;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
// Initialize stepper hardware
|
// Initialize stepper hardware
|
||||||
static void init();
|
static void init();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user