Added option to setting $63 to set spindle RPM to minimum (typically 0) on feed hold. Ref. issue #991.

Fixed reset (abort) not turning off laser in some circumstances.
This commit is contained in:
Terje Io
2026-08-13 18:47:32 +02:00
parent 29b7471fe5
commit 553c501f35
11 changed files with 70 additions and 23 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
## grblHAL ## ## grblHAL ##
Latest build date is 20260811, see the [changelog](changelog.md) for details. Latest build date is 20260813, see the [changelog](changelog.md) for details.
> [!NOTE] > [!NOTE]
> A settings reset will be performed on an update of builds prior to 20241208. Backup and restore of settings is recommended. > A settings reset will be performed on an update of builds prior to 20241208. Backup and restore of settings is recommended.
+2 -1
View File
@@ -47,7 +47,8 @@ PROGMEM static const alarm_detail_t alarm_detail[] = {
{ Alarm_HomingFail, "Homing fail. Bad configuration." }, { Alarm_HomingFail, "Homing fail. Bad configuration." },
{ Alarm_ModbusException, "Modbus exception. Timeout or message error." }, { Alarm_ModbusException, "Modbus exception. Timeout or message error." },
{ Alarm_ExpanderException, "I/O expander communication failed." }, { Alarm_ExpanderException, "I/O expander communication failed." },
{ Alarm_NVS_Failed, "Non Volatile Storage (EEPROM) failure." } { Alarm_NVS_Failed, "Non Volatile Storage (EEPROM) failure." },
{ Alarm_BufferOverflow, "Buffer overflow." }
}; };
static alarm_details_t details = { static alarm_details_t details = {
+3 -2
View File
@@ -3,7 +3,7 @@
Part of grblHAL Part of grblHAL
Copyright (c) 2017-2025 Terje Io Copyright (c) 2017-2026 Terje Io
Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
@@ -49,7 +49,8 @@ typedef enum {
Alarm_ModbusException = 19, //!< 19 Alarm_ModbusException = 19, //!< 19
Alarm_ExpanderException = 20, //!< 20 Alarm_ExpanderException = 20, //!< 20
Alarm_NVS_Failed = 21, //!< 21 Alarm_NVS_Failed = 21, //!< 21
Alarm_AlarmMax = Alarm_NVS_Failed Alarm_BufferOverflow = 22, //!< 22
Alarm_AlarmMax = Alarm_BufferOverflow
} __attribute__ ((__packed__)) alarm_code_t; } __attribute__ ((__packed__)) alarm_code_t;
typedef struct { typedef struct {
+24
View File
@@ -1,5 +1,29 @@
## grblHAL changelog ## grblHAL changelog
<a name="20260813">Build 20260813
Core:
* Added option to setting `$63` - _Feed hold actions_ to set spindle RPM to minimum \(typically 0\) on feed hold.
Ref. issue [#991](https://github.com/grblHAL/core/issues/991).
> [!NOTE]
> This is ignored for laser enabled spindles when M4 is active.
* Fixed reset \(abort\) not turning off laser in some circumstances.
Drivers:
* ESP32: fixed pin conflict with Modbus port for BTT Rodent board. Ref. [#211](https://github.com/grblHAL/ESP32/issues/211).
Fixed issues with second PWM spindle.
Plugins:
* Laser, LigthBurn clusters: Added fix for for modal state RPM left incorrect after processing line.
"Hardened" code to mostly ignore malformed/malign input, may raise alarm if really off the charts.
Ref. PR[#4](/https://github.com/grblHAL/Plugins_laser/pull/4).
---
<a name="20260811">Build 20260811 <a name="20260811">Build 20260811
Core: Core:
+1 -1
View File
@@ -42,7 +42,7 @@
#else #else
#define GRBL_VERSION "1.1f" #define GRBL_VERSION "1.1f"
#endif #endif
#define GRBL_BUILD 20260811 #define GRBL_BUILD 20260813
#define GRBL_URL "https://github.com/grblHAL" #define GRBL_URL "https://github.com/grblHAL"
+5 -3
View File
@@ -179,10 +179,12 @@ bool mc_line (float *target, plan_line_data_t *pl_data)
// Plan and queue motion into planner buffer. // Plan and queue motion into planner buffer.
// While in M3 laser mode also set spindle state and force a buffer sync // While in M3 laser mode also set spindle state and force a buffer sync
// if there is a coincident position passed. // if there is a coincident position passed.
if(!plan_buffer_line(target, pl_data) && pl_data->spindle.hal->cap.laser && pl_data->spindle.state.on && !pl_data->spindle.state.ccw) { if(!plan_buffer_line(target, pl_data) &&
protocol_buffer_synchronize(); pl_data->spindle.hal->cap.laser &&
pl_data->spindle.state.on &&
!pl_data->spindle.state.ccw &&
protocol_buffer_synchronize())
pl_data->spindle.hal->set_state(pl_data->spindle.hal, pl_data->spindle.state, pl_data->spindle.rpm); pl_data->spindle.hal->set_state(pl_data->spindle.hal, pl_data->spindle.state, pl_data->spindle.rpm);
}
#ifdef KINEMATICS_API #ifdef KINEMATICS_API
if(pl_data->condition.jog_motion) { if(pl_data->condition.jog_motion) {
+3 -2
View File
@@ -1226,6 +1226,7 @@ static status_code_t set_hold_actions (setting_id_t id, uint_fast16_t int_value)
{ {
settings.flags.disable_laser_during_hold = bit_istrue(int_value, bit(0)); settings.flags.disable_laser_during_hold = bit_istrue(int_value, bit(0));
settings.flags.restore_after_feed_hold = bit_istrue(int_value, bit(1)); settings.flags.restore_after_feed_hold = bit_istrue(int_value, bit(1));
settings.flags.set_rpm_0_during_hold = bit_istrue(int_value, bit(2));
return Status_OK; return Status_OK;
} }
@@ -1762,7 +1763,7 @@ FLASHMEM static uint32_t get_int (setting_id_t id)
break; break;
case Setting_HoldActions: case Setting_HoldActions:
value = settings.flags.disable_laser_during_hold | (settings.flags.restore_after_feed_hold << 1); value = settings.flags.disable_laser_during_hold | (settings.flags.restore_after_feed_hold << 1) | (settings.flags.set_rpm_0_during_hold << 2);
break; break;
case Setting_ForceInitAlarm: case Setting_ForceInitAlarm:
@@ -2392,7 +2393,7 @@ PROGMEM static const setting_detail_t setting_detail[] = {
{ Setting_DoorOptions, Group_SafetyDoor, "Safety door options", NULL, Format_Bitfield, door_options, NULL, NULL, Setting_IsExtended, &settings.safety_door.flags.value, NULL, is_setting_available }, { Setting_DoorOptions, Group_SafetyDoor, "Safety door options", NULL, Format_Bitfield, door_options, NULL, NULL, Setting_IsExtended, &settings.safety_door.flags.value, NULL, is_setting_available },
#endif #endif
{ Setting_SleepEnable, Group_General, "Sleep enable", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_sleep_enable, get_int, is_setting_available }, { Setting_SleepEnable, Group_General, "Sleep enable", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_sleep_enable, get_int, is_setting_available },
{ Setting_HoldActions, Group_General, "Feed hold actions", NULL, Format_Bitfield, "Disable laser during hold,Restore spindle and coolant state on resume", NULL, NULL, Setting_IsExtendedFn, set_hold_actions, get_int, NULL }, { Setting_HoldActions, Group_General, "Feed hold actions", NULL, Format_Bitfield, "Disable laser during hold,Restore spindle and coolant state on resume,Set RPM to minimum (except for laser M4)", NULL, NULL, Setting_IsExtendedFn, set_hold_actions, get_int, NULL },
{ Setting_ForceInitAlarm, Group_General, "Force init alarm", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_force_initialization_alarm, get_int, NULL }, { Setting_ForceInitAlarm, Group_General, "Force init alarm", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_force_initialization_alarm, get_int, NULL },
{ Setting_ProbingFlags, Group_Probing, "Probing options", NULL, Format_Bitfield, probing_options, NULL, NULL, Setting_IsExtendedFn, set_probe_flags, get_int, is_setting_available }, { Setting_ProbingFlags, Group_Probing, "Probing options", NULL, Format_Bitfield, probing_options, NULL, NULL, Setting_IsExtendedFn, set_probe_flags, get_int, is_setting_available },
#if ENABLE_SPINDLE_LINEARIZATION #if ENABLE_SPINDLE_LINEARIZATION
+2 -1
View File
@@ -611,7 +611,8 @@ typedef union {
m98_prescan_enable :1, m98_prescan_enable :1,
rotary_fix_enable :1, rotary_fix_enable :1,
revert_metric_conversion :1, // For rotary axes inch/min -> mm/min revert_metric_conversion :1, // For rotary axes inch/min -> mm/min
unassigned :6; set_rpm_0_during_hold :1,
unassigned :5;
}; };
} settingflags_t; } settingflags_t;
+5 -2
View File
@@ -845,11 +845,13 @@ FLASHMEM bool spindle_restore (spindle_ptrs_t *spindle, spindle_state_t state, f
{ {
bool ok; bool ok;
if(spindle->cap.laser) { // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts. if(!spindle->param->option.restore_rpm && spindle->cap.laser) { // When in laser mode, ignore spindle spin-up delay. Set to turn on laser when cycle starts.
if(!(ok = !settings.flags.disable_laser_during_hold)) if(!(ok = !settings.flags.disable_laser_during_hold))
ok = (sys.step_control.update_spindle_rpm = _spindle_set_state(spindle, state, 0.0f, 0)); ok = (sys.step_control.update_spindle_rpm = _spindle_set_state(spindle, state, 0.0f, 0));
} else if(!(ok = spindle_check_state(spindle, state) && spindle->param->rpm == rpm)) } else if(!(ok = !spindle->param->option.restore_rpm && spindle_check_state(spindle, state) && spindle->param->rpm == rpm)) {
ok = spindle_set_state_wait(spindle, state, rpm, delay_ms); ok = spindle_set_state_wait(spindle, state, rpm, delay_ms);
spindle->param->option.restore_rpm = Off;
}
return ok; return ok;
} }
@@ -889,6 +891,7 @@ FLASHMEM void spindle_all_off (bool reset)
spindle->param->rpm = spindle->param->rpm_overridden = 0.0f; spindle->param->rpm = spindle->param->rpm_overridden = 0.0f;
spindle->param->state.value = 0; spindle->param->state.value = 0;
spindle->param->option.restore_rpm = Off;
#ifdef GRBL_ESP32 #ifdef GRBL_ESP32
spindle->esp32_off(spindle); spindle->esp32_off(spindle);
#else #else
+2 -1
View File
@@ -364,7 +364,8 @@ typedef struct spindle_param {
uint8_t ramp_up :1, uint8_t ramp_up :1,
ramp_down :1, ramp_down :1,
ignore_delays :1, ignore_delays :1,
override_disable :1; override_disable :1,
restore_rpm :1;
} option; } option;
spindle_ptrs_t *hal; spindle_ptrs_t *hal;
} spindle_param_t; } spindle_param_t;
+22 -9
View File
@@ -370,13 +370,13 @@ void state_set (sys_state_t new_state)
// Suspend manager. Controls spindle overrides in hold states. // Suspend manager. Controls spindle overrides in hold states.
FLASHMEM void state_suspend_manager (void) FLASHMEM void state_suspend_manager (void)
{ {
if(stateHandler != state_await_resume || !gc_spindle_get(0)->state.on) spindle_t *spindle = &restore_condition.spindle[restore_condition.spindle_num];
if(stateHandler != state_await_resume || !gc_spindle_get(0)->state.on || (spindle->hal && spindle->hal->param->option.restore_rpm))
return; return;
if(sys.override.spindle_stop.value) { if(sys.override.spindle_stop.value) {
spindle_t *spindle = &restore_condition.spindle[restore_condition.spindle_num];
// Handles beginning of spindle stop // Handles beginning of spindle stop
if(sys.override.spindle_stop.initiate) { if(sys.override.spindle_stop.initiate) {
sys.override.spindle_stop.value = 0; // Clear stop override state sys.override.spindle_stop.value = 0; // Clear stop override state
@@ -399,9 +399,9 @@ FLASHMEM void state_suspend_manager (void)
grbl.on_override_changed(OverrideChanged_SpindleState); grbl.on_override_changed(OverrideChanged_SpindleState);
} }
} else if(sys.step_control.update_spindle_rpm && restore_condition.spindle[0].hal->get_state(restore_condition.spindle[0].hal).on) { } else if(sys.step_control.update_spindle_rpm && spindle->hal && spindle->hal->get_state(spindle->hal).on) {
// Handles spindle state during hold. NOTE: Spindle speed overrides may be altered during hold state. // Handles spindle state during hold. NOTE: Spindle speed overrides may be altered during hold state.
state_spindle_restore(&restore_condition.spindle[restore_condition.spindle_num], settings.spindle.on_delay); state_spindle_restore(spindle, settings.spindle.on_delay);
sys.step_control.update_spindle_rpm = Off; sys.step_control.update_spindle_rpm = Off;
} }
} }
@@ -595,6 +595,18 @@ FLASHMEM static void state_await_hold (uint_fast16_t rt_exec)
sys.flags.is_parking = false; sys.flags.is_parking = false;
break; break;
case STATE_HOLD:
{
spindle_t *spindle;
if((spindle = &restore_condition.spindle[restore_condition.spindle_num])->hal && settings.flags.set_rpm_0_during_hold) {
if(spindle->state.on && !(spindle->hal->cap.laser && spindle->state.ccw)) {
spindle->hal->param->option.restore_rpm = settings.flags.restore_after_feed_hold;
spindle->hal->set_state(spindle->hal, spindle->state, spindle->hal->rpm_min);
}
}
}
break;
default: default:
break; break;
} }
@@ -674,18 +686,19 @@ FLASHMEM static void state_await_resume (uint_fast16_t rt_exec)
break; break;
default: default:
if (!settings.flags.restore_after_feed_hold) { if(!settings.flags.restore_after_feed_hold) {
if (!restore_condition.spindle[restore_condition.spindle_num].hal->get_state(restore_condition.spindle[restore_condition.spindle_num].hal).on) if (!restore_condition.spindle[restore_condition.spindle_num].hal->get_state(restore_condition.spindle[restore_condition.spindle_num].hal).on)
gc_spindle_off(); gc_spindle_off();
sys.override.spindle_stop.value = 0; // Clear spindle stop override states sys.override.spindle_stop.value = 0; // Clear spindle stop override states
} else { } else {
if (restore_condition.spindle[restore_condition.spindle_num].state.on != restore_condition.spindle[restore_condition.spindle_num].hal->get_state(restore_condition.spindle[restore_condition.spindle_num].hal).on) { spindle_t *spindle;
if((spindle = &restore_condition.spindle[restore_condition.spindle_num])->hal && (spindle->hal->param->option.restore_rpm || spindle->state.on != spindle->hal->get_state(spindle->hal).on)) {
grbl.report.feedback_message(Message_SpindleRestore); grbl.report.feedback_message(Message_SpindleRestore);
state_spindle_restore(&restore_condition.spindle[restore_condition.spindle_num], settings.spindle.on_delay); state_spindle_restore(spindle, settings.spindle.on_delay);
} }
if (restore_condition.coolant.value != hal.coolant.get_state().value) { if(restore_condition.coolant.value != hal.coolant.get_state().value) {
// NOTE: Laser mode will honor this delay. An exhaust system is often controlled by coolant signals. // NOTE: Laser mode will honor this delay. An exhaust system is often controlled by coolant signals.
coolant_restore(restore_condition.coolant, settings.coolant.on_delay); coolant_restore(restore_condition.coolant, settings.coolant.on_delay);
gc_coolant(restore_condition.coolant); gc_coolant(restore_condition.coolant);