diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h index 215f1f802645..fb8fb628cfed 100644 --- a/Marlin/Configuration_adv.h +++ b/Marlin/Configuration_adv.h @@ -2750,6 +2750,22 @@ //#define REALTIME_REPORTING_COMMANDS #if ENABLED(REALTIME_REPORTING_COMMANDS) //#define FULL_REPORT_TO_HOST_FEATURE // Auto-report the machine status like Grbl CNC + + /** + * If enabled, P000 and R000 commands will apply ramping. + * + * REALTIME_RAMPING_STEP determines the ramping resolution. + * Acceptable values: 125, 250, or 500. + * + * REALTIME_RAMPING_STEP_DURATION controls how quickly the stop is executed. + * Valid range: 1 to 10 — lower values result in faster stops. + */ + //#define REALTIME_RAMPING + #if ENABLED(REALTIME_RAMPING) + #define REALTIME_RAMPING_STEP 125 + #define REALTIME_RAMPING_STEP_DURATION 5 + #endif + #endif /** @@ -3720,6 +3736,9 @@ #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 MIN_SPINDLE_OVERRIDE 10 // (%) (Min. 1) Minimum allowed spindle override + #define MAX_SPINDLE_OVERRIDE 200 // (%) (Max. 255) Maximum allowed spindle override + /** * M3/M4 Power Equation * diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp index 175fd0c315bd..7a5836d34fa9 100644 --- a/Marlin/src/MarlinCore.cpp +++ b/Marlin/src/MarlinCore.cpp @@ -785,6 +785,9 @@ void idle(const bool no_stepper_sleep/*=false*/) { // Manage Heaters (and Watchdog) thermalManager.task(); + // Realtime pause/resume ramping loop + TERN_(REALTIME_RAMPING, updateSoftStopResume()); + // Max7219 heartbeat, animation, etc TERN_(MAX7219_DEBUG, max7219.idle_tasks()); diff --git a/Marlin/src/feature/e_parser.cpp b/Marlin/src/feature/e_parser.cpp index e249d819690c..dc27b127fa48 100644 --- a/Marlin/src/feature/e_parser.cpp +++ b/Marlin/src/feature/e_parser.cpp @@ -60,6 +60,11 @@ extern bool wait_for_user, wait_for_heatup; void quickresume_stepper(); #endif +#if ENABLED(REALTIME_RAMPING) + bool realtime_ramping_pause_flag, // = false + realtime_ramping_resume_flag; // = false +#endif + void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { auto uppercase = [](char c) { return TERN0(GCODE_CASE_INSENSITIVE, WITHIN(c, 'a', 'z')) ? c + 'A' - 'a' : c; @@ -207,8 +212,8 @@ void EmergencyParser::update(EmergencyParser::State &state, const uint8_t c) { #endif #if ENABLED(REALTIME_REPORTING_COMMANDS) case EP_GRBL_STATUS: report_current_position_moving(); break; - case EP_GRBL_PAUSE: quickpause_stepper(); break; - case EP_GRBL_RESUME: quickresume_stepper(); break; + case EP_GRBL_PAUSE: TERN(REALTIME_RAMPING, realtime_ramping_pause_flag = true, quickpause_stepper()); break; + case EP_GRBL_RESUME: TERN(REALTIME_RAMPING, realtime_ramping_resume_flag = true, quickresume_stepper()); break; #endif #if ENABLED(SOFT_RESET_VIA_SERIAL) case EP_KILL: hal.reboot(); break; diff --git a/Marlin/src/feature/e_parser.h b/Marlin/src/feature/e_parser.h index 8dacb0581c12..ab5e400dd481 100644 --- a/Marlin/src/feature/e_parser.h +++ b/Marlin/src/feature/e_parser.h @@ -84,3 +84,7 @@ class EmergencyParser { }; extern EmergencyParser emergency_parser; + +#if ENABLED(REALTIME_RAMPING) + extern bool realtime_ramping_pause_flag, realtime_ramping_resume_flag; +#endif diff --git a/Marlin/src/feature/spindle_laser.cpp b/Marlin/src/feature/spindle_laser.cpp index 5f0ea7dd7be7..4aecdac5f711 100644 --- a/Marlin/src/feature/spindle_laser.cpp +++ b/Marlin/src/feature/spindle_laser.cpp @@ -63,12 +63,17 @@ cutter_power_t SpindleLaser::menuPower = 0, // Power v cutter_frequency_t SpindleLaser::frequency; // PWM frequency setting; range: 2K - 50K +#if ENABLED(SPINDLE_FEATURE) + uint16_t SpindleLaser::spindle_override; // M222 Power Override for the Spindle +#endif + #define SPINDLE_LASER_PWM_OFF TERN(SPINDLE_LASER_PWM_INVERT, 255, 0) /** * Init the cutter to a safe OFF state */ void SpindleLaser::init() { + TERN_(SPINDLE_FEATURE, spindle_override = 100); #if ENABLED(SPINDLE_SERVO) servo[SPINDLE_SERVO_NR].move(SPINDLE_SERVO_MIN); #elif PIN_EXISTS(SPINDLE_LASER_ENA) @@ -100,7 +105,12 @@ void SpindleLaser::init() { * * @param ocr Power value */ - void SpindleLaser::_set_ocr(const uint8_t ocr) { + void SpindleLaser::_set_ocr(const uint8_t unscaledOcr) { + + // Apply spindle override + const uint16_t scaled = MUL_TERN(static_cast(unscaledOcr), spindle_override); + const uint8_t ocr = TERN(SPINDLE_FEATURE, scaled > 25500 ? 255 : scaled / 100, scaled > 255 ? 255 : scaled); + #if ENABLED(HAL_CAN_SET_PWM_FREQ) && SPINDLE_LASER_FREQUENCY hal.set_pwm_frequency(pin_t(SPINDLE_LASER_PWM_PIN), frequency); #endif diff --git a/Marlin/src/feature/spindle_laser.h b/Marlin/src/feature/spindle_laser.h index a283f0786d46..490e88fdf7ad 100644 --- a/Marlin/src/feature/spindle_laser.h +++ b/Marlin/src/feature/spindle_laser.h @@ -114,6 +114,10 @@ class SpindleLaser { static cutter_power_t menuPower, // Power as set via LCD menu in PWM, Percentage, or RPM unitPower; // Power as displayed status in PWM, Percentage, or RPM + #if ENABLED(SPINDLE_FEATURE) + static uint16_t spindle_override; // Spindle speed override by percentage + #endif + #if HAS_SPINDLE_ACCELERATION static uint32_t acceleration_spindle_deg_per_s2; // (°/s/s) Spindle acceleration #endif @@ -135,7 +139,7 @@ class SpindleLaser { private: - static void _set_ocr(const uint8_t ocr); + static void _set_ocr(const uint8_t unscaledOcr); public: diff --git a/Marlin/src/gcode/config/M222.cpp b/Marlin/src/gcode/config/M222.cpp new file mode 100644 index 000000000000..7c5f4302eaf5 --- /dev/null +++ b/Marlin/src/gcode/config/M222.cpp @@ -0,0 +1,45 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2025 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 . + * + */ + +#include "../gcode.h" +#include "../../feature/spindle_laser.h" + +#if ENABLED(SPINDLE_FEATURE) + +/** + * M222: Set/read spindle override (M222 S120) + */ + +void GcodeSuite::M222() { + if (parser.seenval('S')) { + const uint8_t new_percentage = constrain((uint8_t)parser.value_int(), MIN_SPINDLE_OVERRIDE, MAX_SPINDLE_OVERRIDE); + if (new_percentage != cutter.spindle_override) { + cutter.spindle_override = new_percentage; + if (cutter.enable_state) + cutter.set_ocr(cutter.upower_to_ocr(cutter.unitPower)); + } + } + + SERIAL_ECHOLNPGM("SPINDLE-OVERRIDE:", cutter.spindle_override); +} + +#endif // SPINDLE_FEATURE diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp index b09fd6905657..ecdb8a1074ba 100644 --- a/Marlin/src/gcode/gcode.cpp +++ b/Marlin/src/gcode/gcode.cpp @@ -770,6 +770,10 @@ void GcodeSuite::process_parsed_command(bool no_ok/*=false*/) { case 221: M221(); break; // M221: Set Flow Percentage #endif + #if ENABLED(SPINDLE_FEATURE) + case 222: M222(); break; // M222: Spindle override + #endif + #if ENABLED(DIRECT_PIN_CONTROL) case 226: M226(); break; // M226: Wait until a pin reaches a state #endif diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 0e5d8f568146..d4a5831434f9 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -204,6 +204,7 @@ * M220 - Set Feedrate Percentage: 'M220 S' (i.e., "FR" on the LCD) * Use 'M220 B' to back up the Feedrate Percentage and 'M220 R' to restore it. (Requires an MMU_MODEL version 2 or 2S) * M221 - Set Flow Percentage: 'M221 S' (Requires an extruder) + * M222 - Set Spindle Override: 'M222 S' (Requires SPINDLE_FEATURE) * M226 - Wait until a pin is in a given state: 'M226 P S' (Requires DIRECT_PIN_CONTROL) * M240 - Trigger a camera to take a photograph. (Requires PHOTO_GCODE) * M250 - Set LCD contrast: 'M250 C' (0-63). (Requires LCD support) @@ -937,6 +938,10 @@ class GcodeSuite { static void M221(); #endif + #if ENABLED(SPINDLE_FEATURE) + static void M222(); + #endif + #if ENABLED(DIRECT_PIN_CONTROL) static void M226(); #endif diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 8ac9ce4db9f2..b82ce8ba6be9 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -4163,7 +4163,16 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive." static_assert(LASER_SAFETY_TIMEOUT_MS < (DEFAULT_STEPPER_TIMEOUT_SEC) * 1000UL, "LASER_SAFETY_TIMEOUT_MS must be less than DEFAULT_STEPPER_TIMEOUT_SEC (" STRINGIFY(DEFAULT_STEPPER_TIMEOUT_SEC) " seconds)"); #endif -#endif + #if ENABLED(SPINDLE_FEATURE) + #if !WITHIN(MIN_SPINDLE_OVERRIDE, 1, 100) + #error "MIN_SPINDLE_OVERRIDE must be an integer power value from 1 to 100." + #elif !WITHIN(MAX_SPINDLE_OVERRIDE, 100, 255) + #error "MAX_SPINDLE_OVERRIDE must be an integer power value from 100 to 255." + #elif MAX_SPINDLE_OVERRIDE < MIN_SPINDLE_OVERRIDE + #error "MAX_SPINDLE_OVERRIDE must be >= MIN_SPINDLE_OVERRIDE." + #endif + #endif +#endif // HAS_CUTTER #if ENABLED(COOLANT_CONTROL) #if NONE(COOLANT_MIST, COOLANT_FLOOD) diff --git a/Marlin/src/inc/Warnings.cpp b/Marlin/src/inc/Warnings.cpp index f11eedbe52f5..8ce859571aa4 100644 --- a/Marlin/src/inc/Warnings.cpp +++ b/Marlin/src/inc/Warnings.cpp @@ -927,10 +927,10 @@ #endif /** - * User doesn't have or disabled G92? + * User doesn't have or disabled M92? */ #if DISABLED(EDITABLE_STEPS_PER_UNIT) - #warning "EDITABLE_STEPS_PER_UNIT is required to enable G92 runtime configuration of steps-per-unit." + #warning "EDITABLE_STEPS_PER_UNIT is required to enable M92 runtime configuration of steps-per-unit." #endif /** diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp index 227e6ff47ec4..714c39ce6e56 100644 --- a/Marlin/src/module/motion.cpp +++ b/Marlin/src/module/motion.cpp @@ -77,6 +77,10 @@ #include "../feature/bedlevel/bdl/bdl.h" #endif +#if ENABLED(REALTIME_RAMPING) + #include "../feature/e_parser.h" +#endif + // Relative Mode. Enable with G91, disable with G90. bool relative_mode; // = false @@ -2860,3 +2864,80 @@ void set_axis_is_at_home(const AxisEnum axis) { home_offset[axis] = v; } #endif + +#if ENABLED(REALTIME_RAMPING) + + static bool smooth_motion_flag = false; + static millis_t smooth_motion_start = 0; + static bool smooth_stopped_flag = false; + + void realtime_soft_stop() { + if (!smooth_motion_flag) { + smooth_motion_flag = true; + stepper.isr_ramp_factor = MAX_REALTIME_RAMPING_FACTOR; + smooth_motion_start = millis(); + } + + if (stepper.isr_ramp_factor <= MIN_REALTIME_RAMPING_FACTOR) { + stepper.isr_ramp_factor = MIN_REALTIME_RAMPING_FACTOR; + smooth_motion_flag = false; + realtime_ramping_pause_flag = false; + smooth_stopped_flag = true; + quickpause_stepper(); + set_and_report_grblstate(M_HOLD); + return; + } + + const millis_t smooth_now = millis(); + if (smooth_now - smooth_motion_start >= REALTIME_RAMPING_STEP_DURATION) { + stepper.isr_ramp_factor -= REALTIME_RAMPING_STEP; + smooth_motion_start = smooth_now; + } + } + + void realtime_soft_resume() { + if (!smooth_motion_flag) { + smooth_motion_flag = true; + stepper.isr_ramp_factor = MIN_REALTIME_RAMPING_FACTOR; + smooth_motion_start = millis(); + + if (!stepper.is_awake()) stepper.wake_up(); + + if ( TERN0(HAS_X_AXIS, stepper.axis_is_moving(X_AXIS)) + || TERN0(HAS_Y_AXIS, stepper.axis_is_moving(Y_AXIS)) + || TERN0(HAS_Z_AXIS, stepper.axis_is_moving(Z_AXIS)) + || TERN0(HAS_EXTRUDERS, stepper.axis_is_moving(E_AXIS)) + ) { + set_and_report_grblstate(M_RUNNING); + } + else { + set_and_report_grblstate(M_IDLE); + } + } + + if (stepper.isr_ramp_factor >= MAX_REALTIME_RAMPING_FACTOR) { + stepper.isr_ramp_factor = MAX_REALTIME_RAMPING_FACTOR; + smooth_motion_flag = false; + realtime_ramping_resume_flag = false; + return; + } + + const millis_t smooth_now = millis(); + + if (smooth_now - smooth_motion_start >= REALTIME_RAMPING_STEP_DURATION) { + stepper.isr_ramp_factor += REALTIME_RAMPING_STEP; + smooth_motion_start = smooth_now; + } + } + + void updateSoftStopResume() { + if (realtime_ramping_pause_flag) { + realtime_ramping_resume_flag = false; // Prioritize pause in case of a conflict + realtime_soft_stop(); + } + else if (realtime_ramping_resume_flag) { + realtime_soft_resume(); + } + } + +#endif // REALTIME_RAMPING diff --git a/Marlin/src/module/motion.h b/Marlin/src/module/motion.h index edee41a903e5..d1c98b9ccab5 100644 --- a/Marlin/src/module/motion.h +++ b/Marlin/src/module/motion.h @@ -652,3 +652,9 @@ void home_if_needed(const bool keeplev=false); void set_homing_current(const AxisEnum axis); void restore_homing_current(const AxisEnum axis); #endif + +#if ENABLED(REALTIME_RAMPING) + void realtime_soft_stop(); + void realtime_soft_resume(); + void updateSoftStopResume(); +#endif diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp index 6e1212960384..b91a59cf5d4f 100644 --- a/Marlin/src/module/stepper.cpp +++ b/Marlin/src/module/stepper.cpp @@ -150,6 +150,10 @@ Stepper stepper; // Singleton // public: +#if ENABLED(REALTIME_RAMPING) + volatile uint16_t Stepper::isr_ramp_factor; +#endif + #if ANY(HAS_EXTRA_ENDSTOPS, Z_STEPPER_AUTO_ALIGN) bool Stepper::separate_multi_axis = false; #endif @@ -1713,6 +1717,15 @@ void Stepper::isr() { // Now 'next_isr_ticks' contains the period to the next Stepper ISR - And we are // sure that the time has not arrived yet - Warrantied by the scheduler + #if ENABLED(REALTIME_RAMPING) + if (isr_ramp_factor != MAX_REALTIME_RAMPING_FACTOR) { + // Scale the ISR frequency by isr_ramp_factor + uint32_t temp_isr_ticks = (uint32_t)next_isr_ticks * 10000UL / isr_ramp_factor; + //LIMIT(temp_isr_ticks, 0U, 65535); + next_isr_ticks = (hal_timer_t)temp_isr_ticks; + } + #endif + // Set the next ISR to fire at the proper time HAL_timer_set_compare(MF_TIMER_STEP, next_isr_ticks); @@ -3135,6 +3148,10 @@ bool Stepper::is_block_busy(const block_t * const block) { void Stepper::init() { + #if ENABLED(REALTIME_RAMPING) + isr_ramp_factor = MAX_REALTIME_RAMPING_FACTOR; + #endif + #if MB(ALLIGATOR) const float motor_current[] = MOTOR_CURRENT; unsigned int digipot_motor = 0; diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h index 330ff9f6b6a6..f82fc6c88629 100644 --- a/Marlin/src/module/stepper.h +++ b/Marlin/src/module/stepper.h @@ -318,6 +318,12 @@ constexpr ena_mask_t enable_overlap[] = { #endif // NONLINEAR_EXTRUSION +// Pause resume ramping constants +#if ENABLED(REALTIME_RAMPING) + #define MIN_REALTIME_RAMPING_FACTOR 500 + #define MAX_REALTIME_RAMPING_FACTOR 10000 +#endif + // // Stepper class definition // @@ -329,6 +335,11 @@ class Stepper { public: + // Pause resume ramping factor + #if ENABLED(REALTIME_RAMPING) + static volatile uint16_t isr_ramp_factor; + #endif + // The minimal step rate ensures calculations stay within limits // and avoid the most unreasonably slow step rates. static constexpr uint32_t minimal_step_rate = ( diff --git a/ini/features.ini b/ini/features.ini index 1f09858f07a7..cd776058fb8c 100644 --- a/ini/features.ini +++ b/ini/features.ini @@ -267,6 +267,7 @@ HAS_FILAMENT_SENSOR = build_src_filter=+ + MK2_MULTIPLEXER = build_src_filter=+ HAS_CUTTER = build_src_filter=+ + +SPINDLE_FEATURE = build_src_filter=+ HAS_DRIVER_SAFE_POWER_PROTECT = build_src_filter=+ EXPERIMENTAL_I2CBUS = build_src_filter=+ + G26_MESH_VALIDATION = build_src_filter=+