Various Laser / Spindle improvements (#15335)

This commit is contained in:
Ben 2020-04-03 01:31:08 +01:00 committed by GitHub
parent e7e9304819
commit df8b7dfc40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 776 additions and 169 deletions

View file

@ -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_SLOPE 0.3922 // SPEED_POWER_SLOPE = SPEED_POWER_MAX / 255
#define SPEED_POWER_INTERCEPT 0 #define SPEED_POWER_INTERCEPT 0
#define SPEED_POWER_MIN 10 #define SPEED_POWER_MIN 0
#define SPEED_POWER_MAX 100 // 0-100% #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

View file

@ -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

View file

@ -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__

View file

@ -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

View file

@ -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>

View file

@ -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
#if !HAS_CUTTER
thermalManager.zero_fan_speeds(); 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();
@ -792,11 +804,11 @@ void minkill(const bool steppers_off/*=false*/) {
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
} }
/** /**

View file

@ -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

View file

@ -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,40 +52,38 @@ 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
@ -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)

View file

@ -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;

View 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

View file

@ -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,9 +77,26 @@
*/ */
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 #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_direction(is_M4); cutter.set_direction(is_M4);
@ -81,19 +104,25 @@ void GcodeSuite::M3_M4(const bool is_M4) {
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);
} }

View file

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

View file

@ -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) {

View file

@ -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;

View file

@ -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

View file

@ -324,8 +324,25 @@
/** /**
* 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 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
#if PIN_EXISTS(SD_DETECT)
#if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION)) #if HAS_LCD_MENU && (SD_CONNECTION_IS(LCD) || !defined(SDCARD_CONNECTION))
#undef SD_DETECT_STATE #undef SD_DETECT_STATE
#if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) #if ENABLED(ELB_FULL_GRAPHIC_CONTROLLER)
@ -335,6 +352,8 @@
#ifndef SD_DETECT_STATE #ifndef SD_DETECT_STATE
#define SD_DETECT_STATE LOW #define SD_DETECT_STATE LOW
#endif #endif
#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

View file

@ -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."

View file

@ -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));
#if CUTTER_DISPLAY_IS(PERCENT)
lcd_put_wchar('%'); lcd_put_wchar('%');
#elif CUTTER_DISPLAY_IS(RPM)
lcd_put_wchar('K');
#endif
} }
#endif #endif

View file

@ -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");

View file

@ -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(CUTTER_MENU_TYPE, MSG_CUTTER(POWER), &cutter.power, SPEED_POWER_MIN, SPEED_POWER_MAX); EDIT_ITEM_FAST(CUTTER_MENU_POWER_TYPE, MSG_CUTTER(POWER), &cutter.setPower, cutter.interpret_power(SPEED_POWER_MIN), cutter.interpret_power(SPEED_POWER_MAX),
#endif []{
ACTION_ITEM(MSG_CUTTER(OFF), cutter.disable); if (cutter.isOn) {
cutter.power = cutter.setPower;
} }
});
#endif
if (cutter.enabled() && cutter.isOn)
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();
} }

View file

@ -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

View file

@ -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)

View file

@ -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,8 +1692,7 @@ 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
@ -1690,9 +1707,40 @@ uint32_t Stepper::block_phase_isr() {
acceleration_time += interval; acceleration_time += interval;
#if ENABLED(LIN_ADVANCE) #if ENABLED(LIN_ADVANCE)
if (LA_use_advance_lead) {
// Fire ISR if final adv_rate is reached // Fire ISR if final adv_rate is reached
if (LA_steps && (!LA_use_advance_lead || LA_isr_rate != current_block->advance_speed)) if (LA_steps && LA_isr_rate != current_block->advance_speed) nextAdvanceISR = 0;
initiateLA(); }
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
} }
// We must be in cruise phase otherwise #endif
}
// 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

View file

@ -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();