SOFT_FEED_HOLD (#28265)

This commit is contained in:
DerAndere 2026-01-21 04:58:28 +01:00 committed by GitHub
parent 367e2219e3
commit 7c29cdb685
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 270 additions and 36 deletions

View file

@ -2826,8 +2826,8 @@
*
* Adds support for commands:
* S000 : Report State and Position while moving.
* P000 : Instant Pause / Hold while moving.
* R000 : Resume from Pause / Hold.
* P000 : Instant Pause / Hold while moving. Enable SOFT_FEED_HOLD for soft deceleration.
* R000 : Resume from Pause / Hold. Enable SOFT_FEED_HOLD for soft acceleration.
*
* - During Hold all Emergency Parser commands are available, as usual.
* - Enable NANODLP_Z_SYNC and NANODLP_ALL_AXIS for move command end-state reports.
@ -4457,15 +4457,38 @@
#endif
/**
* Instant freeze / unfreeze functionality
* Potentially useful for rapid stop that allows being resumed. Halts stepper movement.
* Note this does NOT pause spindles, lasers, fans, heaters or any other auxiliary device.
* @section interface
* Freeze / Unfreeze
*
* Pause / Hold that keeps power available and does not stop the spindle can be initiated by
* the FREEZE_PIN. Halts instantly (default) or performs a soft feed hold that decelerates and
* halts movement at FREEZE_JERK (requires SOFT_FEED_HOLD).
* Motion can be resumed by using the FREEZE_PIN.
*
* NOTE: Controls Laser PWM but does NOT pause Spindle, Fans, Heaters or other devices.
* @section freeze
*/
//#define FREEZE_FEATURE
#if ENABLED(FREEZE_FEATURE)
//#define FREEZE_PIN 41 // Override the default (KILL) pin here
#define FREEZE_STATE LOW // State of pin indicating freeze
//#define FREEZE_PIN -1 // Override the default (KILL) pin here
#define FREEZE_STATE LOW // State of pin indicating freeze
#endif
#if ANY(FREEZE_FEATURE, REALTIME_REPORTING_COMMANDS)
/**
* Command P000 (REALTIME_REPORTING_COMMANDS and EMERGENCY_PARSER) or
* FREEZE_PIN (FREEZE_FEATURE) initiates a soft feed hold that keeps
* power available and does not stop the spindle.
*
* The soft feed hold decelerates and halts movement at FREEZE_JERK.
* Motion can be resumed with command R000 (requires REALTIME_REPORTING_COMMANDS) or
* by using the FREEZE_PIN (requires FREEZE_FEATURE).
*
* NOTE: Affects Laser PWM but DOES NOT pause Spindle, Fans, Heaters or other devices.
*/
//#define SOFT_FEED_HOLD
#if ENABLED(SOFT_FEED_HOLD)
#define FREEZE_JERK 2 // (mm/s) Completely halt when motion has decelerated below this value
#endif
#endif
/**

View file

@ -260,6 +260,10 @@
#include "feature/rs485.h"
#endif
#if ENABLED(SOFT_FEED_HOLD)
#include "feature/e_parser.h"
#endif
/**
* Spin in place here while keeping temperature processing alive
*/
@ -514,8 +518,14 @@ void Marlin::manage_inactivity(const bool no_stepper_sleep/*=false*/) {
}
#endif
#if ENABLED(FREEZE_FEATURE)
stepper.frozen = READ(FREEZE_PIN) == FREEZE_STATE;
// Handle the FREEZE button
#if ANY(FREEZE_FEATURE, SOFT_FEED_HOLD)
stepper.set_frozen_triggered(
TERN0(FREEZE_FEATURE, READ(FREEZE_PIN) == FREEZE_STATE)
#if ALL(SOFT_FEED_HOLD, REALTIME_REPORTING_COMMANDS)
|| realtime_ramping_pause_flag
#endif
);
#endif
#if HAS_HOME
@ -1221,7 +1231,7 @@ void setup() {
#endif
#endif
#if ENABLED(FREEZE_FEATURE)
#if ENABLED(FREEZE_FEATURE) && DISABLED(NO_FREEZE_PIN)
SETUP_LOG("FREEZE_PIN");
#if FREEZE_STATE
SET_INPUT_PULLDOWN(FREEZE_PIN);

View file

@ -60,6 +60,10 @@ EmergencyParser emergency_parser;
void quickresume_stepper();
#endif
#if ENABLED(SOFT_FEED_HOLD)
bool realtime_ramping_pause_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;
@ -223,8 +227,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(SOFT_FEED_HOLD, realtime_ramping_pause_flag = true, quickpause_stepper()); break;
case EP_GRBL_RESUME: TERN(SOFT_FEED_HOLD, realtime_ramping_pause_flag = false, quickresume_stepper()); break;
#endif
#if ENABLED(SOFT_RESET_VIA_SERIAL)
case EP_KILL: hal.reboot(); break;

View file

@ -93,3 +93,7 @@ private:
};
extern EmergencyParser emergency_parser;
#if ENABLED(SOFT_FEED_HOLD)
extern bool realtime_ramping_pause_flag;
#endif

View file

@ -3047,9 +3047,10 @@
#endif
// User Interface
#if ENABLED(FREEZE_FEATURE) && !PIN_EXISTS(FREEZE) && PIN_EXISTS(KILL)
#if ENABLED(FREEZE_FEATURE) && DISABLED(NO_FREEZE_PIN) && !PIN_EXISTS(FREEZE) && PIN_EXISTS(KILL)
#define FREEZE_PIN KILL_PIN
#elif PIN_EXISTS(KILL) && TERN1(FREEZE_FEATURE, KILL_PIN != FREEZE_PIN)
#define FREEZE_STOLE_KILL_PIN_WARNING 1
#elif PIN_EXISTS(KILL) && TERN1(HAS_FREEZE_PIN, KILL_PIN != FREEZE_PIN)
#define HAS_KILL 1
#endif
#if PIN_EXISTS(HOME)

View file

@ -579,8 +579,12 @@ static_assert(COUNT(arm) == LOGICAL_AXES, "AXIS_RELATIVE_MODES must contain " _L
/**
* Instant Freeze
*/
#if ENABLED(FREEZE_FEATURE) && !(PIN_EXISTS(FREEZE) && defined(FREEZE_STATE))
#error "FREEZE_FEATURE requires both FREEZE_PIN and FREEZE_STATE."
#if ENABLED(SOFT_FEED_HOLD) && !defined(FREEZE_JERK)
#error "SOFT_FEED_HOLD requires FREEZE_JERK."
#elif ENABLED(FREEZE_FEATURE) && DISABLED(NO_FREEZE_PIN) && !(defined(FREEZE_PIN) && defined(FREEZE_STATE))
#error "FREEZE_FEATURE requires FREEZE_PIN and FREEZE_STATE."
#elif ENABLED(NO_FREEZE_PIN) && !(defined(REALTIME_REPORTING_COMMANDS))
#error "NO_FREEZE_PIN requires REALTIME_REPORTING_COMMANDS."
#endif
/**
@ -4501,8 +4505,8 @@ static_assert(_PLUS_TEST(3), "DEFAULT_MAX_ACCELERATION values must be positive."
#error "SMOOTH_LIN_ADVANCE is not yet available in FT_MOTION. Disable NO_STANDARD_MOTION if you require it."
#elif ENABLED(MIXING_EXTRUDER)
#error "MIXING_EXTRUDER is not yet available in FT_MOTION. Disable NO_STANDARD_MOTION if you require it."
#elif ENABLED(FREEZE_FEATURE)
#error "FREEZE_FEATURE is not yet available in FT_MOTION. Disable NO_STANDARD_MOTION if you require it."
#elif ENABLED(SOFT_FEED_HOLD)
#error "SOFT_FEED_HOLD is not yet available in FT_MOTION. Disable NO_STANDARD_MOTION if you require it."
#elif ENABLED(DIRECT_STEPPING)
#error "DIRECT_STEPPING is not yet available in FT_MOTION. Disable NO_STANDARD_MOTION if you require it."
#elif ENABLED(DIFFERENTIAL_EXTRUDER)

View file

@ -1005,3 +1005,10 @@
#elif !RECOMMEND_REINIT_NOISY_LCD && ENABLED(REINIT_NOISY_LCD)
#warning "REINIT_NOISY_LCD is probably not required with your LCD controller model."
#endif
/**
* FREEZE_FEATURE may override the KILL_PIN
*/
#if FREEZE_STOLE_KILL_PIN_WARNING
#warning "FREEZE_FEATURE uses KILL_PIN replacing the KILL button. Define a separate FREEZE_PIN if you don't want this behavior."
#endif

View file

@ -2428,6 +2428,9 @@ bool Planner::_populate_block(
block->acceleration_steps_per_s2 = accel;
#if DISABLED(S_CURVE_ACCELERATION)
block->acceleration_rate = uint32_t(accel * (float(_BV32(24)) / (STEPPER_TIMER_RATE)));
#elif ENABLED(SOFT_FEED_HOLD)
// No need to waste time calculating the linear acceleration rate until the freeze_pin is triggered, leave this 0
block->acceleration_rate = 0;
#endif
#endif
block->acceleration = accel / steps_per_mm;

View file

@ -254,7 +254,8 @@ typedef struct PlannerBlock {
#if ENABLED(S_CURVE_ACCELERATION)
uint32_t acceleration_time_inverse, // Inverse of acceleration and deceleration periods, expressed as integer. Scale depends on CPU being used
deceleration_time_inverse;
#elif HAS_STANDARD_MOTION
#endif
#if ENABLED(HAS_STANDARD_MOTION) && (DISABLED(S_CURVE_ACCELERATION) || ENABLED(FREEZE_FEATURE))
uint32_t acceleration_rate; // Acceleration rate in (2^24 steps)/timer_ticks*s
#endif

View file

@ -199,8 +199,14 @@ uint32_t Stepper::acceleration_time, Stepper::deceleration_time;
constexpr uint8_t Stepper::oversampling_factor; // = 0
#endif
#if ENABLED(FREEZE_FEATURE)
bool Stepper::frozen; // = false
#if ANY(SOFT_FEED_HOLD, FREEZE_FEATURE)
frozen_state_t Stepper::frozen_state; // Frozen flags
#endif
#if ENABLED(SOFT_FEED_HOLD)
uint32_t Stepper::frozen_time; // How much time has passed since frozen_state was triggered?
#if ENABLED(LASER_FEATURE)
uint8_t frozen_last_laser_power; // Saved laser power prior to halting motion
#endif
#endif
// Delta error variables for the Bresenham line tracer
@ -1846,7 +1852,11 @@ void Stepper::isr() {
if (!current_block || step_events_completed >= step_event_count) return;
// Skipping step processing causes motion to freeze
if (TERN0(FREEZE_FEATURE, frozen)) return;
#if ENABLED(SOFT_FEED_HOLD)
if (frozen_state.triggered && frozen_state.solid) return;
#elif ENABLED(FREEZE_FEATURE)
if (frozen_state.state == 0) return;
#endif
// Count of pending loops and events for this iteration
const uint32_t pending_events = step_event_count - step_events_completed;
@ -2418,6 +2428,10 @@ void Stepper::isr() {
// If no queued movements, just wait 1ms for the next block
hal_timer_t interval = (STEPPER_TIMER_RATE) / 1000UL;
// Frozen solid?? Exit and do not fetch blocks.
if (TERN0(SOFT_FEED_HOLD, frozen_state.triggered && frozen_state.solid))
return interval;
// If there is a current block
if (current_block) {
// If current block is finished, reset pointer and finalize state
@ -2460,11 +2474,16 @@ void Stepper::isr() {
// acc_step_rate is in steps/second
// Modify acc_step_rate if the machine is freezing
TERN_(SOFT_FEED_HOLD, check_frozen_time(acc_step_rate));
// step_rate to timer interval and steps per stepper isr
interval = calc_multistep_timer_interval(acc_step_rate << oversampling_factor);
acceleration_time += interval;
deceleration_time = 0; // Reset since we're doing acceleration first.
TERN_(SOFT_FEED_HOLD, check_frozen_state(FREEZE_ACCELERATION, interval));
// Apply Nonlinear Extrusion, if enabled
calc_nonlinear_e(acc_step_rate << oversampling_factor);
@ -2526,10 +2545,14 @@ void Stepper::isr() {
#endif
TERN_(SOFT_FEED_HOLD, check_frozen_time(step_rate));
// step_rate to timer interval and steps per stepper isr
interval = calc_multistep_timer_interval(step_rate << oversampling_factor);
deceleration_time += interval;
TERN_(SOFT_FEED_HOLD, check_frozen_state(FREEZE_DECELERATION, interval));
// Apply Nonlinear Extrusion, if enabled
calc_nonlinear_e(step_rate << oversampling_factor);
@ -2576,21 +2599,25 @@ void Stepper::isr() {
else { // Must be in cruise phase otherwise
// Calculate the ticks_nominal for this nominal speed, if not done yet
if (ticks_nominal == 0) {
if (ticks_nominal == 0 || TERN0(SOFT_FEED_HOLD, frozen_time)) {
uint32_t step_rate = current_block->nominal_rate;
TERN_(SOFT_FEED_HOLD, check_frozen_time(step_rate));
// step_rate to timer interval and loops for the nominal speed
ticks_nominal = calc_multistep_timer_interval(current_block->nominal_rate << oversampling_factor);
ticks_nominal = calc_multistep_timer_interval(step_rate << oversampling_factor);
deceleration_time = ticks_nominal / 2;
// Prepare for deceleration
IF_DISABLED(S_CURVE_ACCELERATION, acc_step_rate = current_block->nominal_rate);
IF_DISABLED(S_CURVE_ACCELERATION, acc_step_rate = step_rate);
TERN_(SMOOTH_LIN_ADVANCE, curr_step_rate = current_block->nominal_rate);
// Apply Nonlinear Extrusion, if enabled
calc_nonlinear_e(current_block->nominal_rate << oversampling_factor);
calc_nonlinear_e(step_rate << oversampling_factor);
#if HAS_ROUGH_LIN_ADVANCE
if (la_active)
la_interval = calc_timer_interval(current_block->nominal_rate >> current_block->la_scaling);
la_interval = calc_timer_interval(step_rate >> current_block->la_scaling);
#endif
// Adjust Laser Power - Cruise
@ -2610,6 +2637,8 @@ void Stepper::isr() {
// The timer interval is just the nominal value for the nominal speed
interval = ticks_nominal;
TERN_(SOFT_FEED_HOLD, check_frozen_state(FREEZE_CRUISE, interval));
}
}
@ -2631,9 +2660,12 @@ void Stepper::isr() {
#endif
}
else { // !current_block
TERN_(SOFT_FEED_HOLD, check_frozen_state(FREEZE_STATIONARY, interval));
#if ENABLED(LASER_FEATURE)
// If no movement in dynamic mode turn Laser off
if (cutter.cutter_mode == CUTTER_MODE_DYNAMIC)
cutter.apply_power(0); // No movement in dynamic mode so turn Laser off
cutter.apply_power(0);
#endif
}
@ -2916,13 +2948,22 @@ void Stepper::isr() {
}
#endif
uint32_t initial_rate = current_block->initial_rate;
#if ENABLED(SOFT_FEED_HOLD)
if (frozen_time) check_frozen_time(initial_rate);
#endif
// Calculate the initial timer interval
interval = calc_multistep_timer_interval(current_block->initial_rate << oversampling_factor);
interval = calc_multistep_timer_interval(initial_rate << oversampling_factor);
TERN_(SOFT_FEED_HOLD, check_frozen_state(FREEZE_ACCELERATION, interval));
// Initialize ac/deceleration time as if half the time passed.
acceleration_time = deceleration_time = interval / 2;
// Apply Nonlinear Extrusion, if enabled
calc_nonlinear_e(current_block->initial_rate << oversampling_factor);
calc_nonlinear_e(initial_rate << oversampling_factor);
#if ENABLED(LIN_ADVANCE)
#if ENABLED(SMOOTH_LIN_ADVANCE)
@ -2930,7 +2971,7 @@ void Stepper::isr() {
#else
if (la_active) {
const uint32_t la_step_rate = la_advance_steps < current_block->max_adv_steps ? current_block->la_advance_rate : 0;
la_interval = calc_timer_interval((current_block->initial_rate + la_step_rate) >> current_block->la_scaling);
la_interval = calc_timer_interval((initial_rate + la_step_rate) >> current_block->la_scaling);
}
#endif
#endif
@ -3876,3 +3917,105 @@ void Stepper::report_positions() {
}
#endif // BABYSTEPPING
#if ENABLED(SOFT_FEED_HOLD)
void Stepper::set_frozen_solid(const bool state) {
if (state == frozen_state.solid) return;
frozen_state.solid = true;
#if ENABLED(LASER_FEATURE)
if (state) {
frozen_last_laser_power = cutter.last_power_applied;
cutter.apply_power(0); // No movement in dynamic mode so turn Laser off
}
else
cutter.apply_power(frozen_last_laser_power); // Restore frozen laser power
#endif
#if ENABLED(REALTIME_REPORTING_COMMANDS)
set_and_report_grblstate(state ? M_HOLD : M_RUNNING);
#endif
}
void Stepper::check_frozen_time(uint32_t &step_rate) {
// If frozen_time is 0 there is no need to modify the current step_rate
if (!frozen_time) return;
#if ENABLED(S_CURVE_ACCELERATION)
// If the machine is configured to use S_CURVE_ACCELERATION standard ramp acceleration
// rate will not have been calculated at this point
if (!current_block->acceleration_rate)
current_block->acceleration_rate = uint32_t(current_block->acceleration_steps_per_s2 * (float(1UL << 24) / (STEPPER_TIMER_RATE)));
#endif
const uint32_t freeze_rate = STEP_MULTIPLY(frozen_time, current_block->acceleration_rate);
const uint32_t min_step_rate = current_block->steps_per_mm * (FREEZE_JERK);
if (step_rate > freeze_rate)
step_rate -= freeze_rate;
else
step_rate = 0;
if (step_rate <= min_step_rate) {
set_frozen_solid(true);
step_rate = min_step_rate;
}
}
void Stepper::check_frozen_state(const FreezePhase phase, const uint32_t interval) {
switch (phase) {
case FREEZE_STATIONARY:
// If triggered while stationary immediately set solid flag
if (frozen_state.triggered) {
frozen_time = 0;
set_frozen_solid(true);
}
else
set_frozen_solid(false);
break;
case FREEZE_ACCELERATION:
// If frozen state is activated during the acceleration phase of a block we need to double our decceleration efforts
if (frozen_state.triggered) {
if (!frozen_state.solid) frozen_time += interval * 2;
}
else
set_frozen_solid(false);
break;
case FREEZE_DECELERATION:
// If frozen state is deactivated during the deceleration phase we need to double our acceleration efforts
if (!frozen_state.triggered) {
if (frozen_time) {
if (frozen_time > interval * 2)
frozen_time -= interval * 2;
else
frozen_time = 0;
}
set_frozen_solid(false);
}
break;
case FREEZE_CRUISE:
// During cruise stage acceleration/deceleration take place at regular rate
if (frozen_state.triggered) {
if (!frozen_state.solid) frozen_time += interval;
}
else {
if (frozen_time) {
if (frozen_time > interval)
frozen_time -= interval;
else {
frozen_time = 0;
ticks_nominal = 0; // Reset ticks_nominal to allow for recalculation of interval at nominal_rate
}
}
set_frozen_solid(false);
}
break;
}
}
#endif // SOFT_FEED_HOLD

View file

@ -318,6 +318,26 @@ constexpr ena_mask_t enable_overlap[] = {
#endif // NONLINEAR_EXTRUSION
#if ANY(FREEZE_FEATURE, SOFT_FEED_HOLD)
typedef union {
uint8_t state;
struct { bool triggered:1, solid:1; };
} frozen_state_t;
enum FrozenState { FROZEN_TRIGGERED, FROZEN_SOLID };
#if ENABLED(SOFT_FEED_HOLD)
enum FreezePhase : uint8_t {
FREEZE_STATIONARY,
FREEZE_ACCELERATION,
FREEZE_DECELERATION,
FREEZE_CRUISE
};
#endif
#endif
//
// Stepper class definition
//
@ -367,8 +387,12 @@ class Stepper {
static constexpr uint8_t last_moved_extruder = 0;
#endif
#if ENABLED(FREEZE_FEATURE)
static bool frozen; // Set this flag to instantly freeze motion
#if ANY(FREEZE_FEATURE, SOFT_FEED_HOLD)
static frozen_state_t frozen_state; // Frozen flags
static void set_frozen_triggered(const bool state) { frozen_state.triggered = state; }
#if ENABLED(SOFT_FEED_HOLD)
static bool is_frozen_triggered() { return frozen_state.triggered; }
#endif
#endif
#if ENABLED(NONLINEAR_EXTRUSION)
@ -800,6 +824,15 @@ class Stepper {
static void ftMotion_stepper();
#endif
#if ENABLED(SOFT_FEED_HOLD)
static uint32_t frozen_time; // How much time passed since frozen_state was triggered?
#if ENABLED(LASER_FEATURE)
static uint8_t frozen_last_laser_power; // Saved laser power prior to halting motion
#endif
static void check_frozen_state(const FreezePhase type, const uint32_t interval);
static void check_frozen_time(uint32_t &step_rate);
static void set_frozen_solid(const bool state);
#endif
};
extern Stepper stepper;

View file

@ -14,8 +14,9 @@ opt_set MOTHERBOARD BOARD_RAMPS4DUE_EFB \
E0_AUTO_FAN_PIN 8 FANMUX0_PIN 53 EXTRUDER_AUTO_FAN_SPEED 100 \
TEMP_SENSOR_CHAMBER 3 TEMP_CHAMBER_PIN 6 HEATER_CHAMBER_PIN 45 \
BACKLASH_MEASUREMENT_FEEDRATE 600 \
TRAMMING_POINT_XY '{{20,20},{20,20},{20,20},{20,20},{20,20}}' TRAMMING_POINT_NAME_5 '"Point 5"'
opt_enable S_CURVE_ACCELERATION EEPROM_SETTINGS GCODE_MACROS GCODE_MACROS_IN_EEPROM \
TRAMMING_POINT_XY '{{20,20},{20,20},{20,20},{20,20},{20,20}}' TRAMMING_POINT_NAME_5 '"Point 5"' \
FREEZE_PIN 17
opt_enable S_CURVE_ACCELERATION FREEZE_FEATURE SOFT_FEED_HOLD EEPROM_SETTINGS GCODE_MACROS GCODE_MACROS_IN_EEPROM \
FIX_MOUNTED_PROBE Z_SAFE_HOMING CODEPENDENT_XY_HOMING \
ASSISTED_TRAMMING REPORT_TRAMMING_MM ASSISTED_TRAMMING_WAIT_POSITION \
EEPROM_SETTINGS SDSUPPORT BINARY_FILE_TRANSFER \