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 ##
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]
> 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_ModbusException, "Modbus exception. Timeout or message error." },
{ 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 = {
+3 -2
View File
@@ -3,7 +3,7 @@
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) 2009-2011 Simen Svale Skogsrud
@@ -49,7 +49,8 @@ typedef enum {
Alarm_ModbusException = 19, //!< 19
Alarm_ExpanderException = 20, //!< 20
Alarm_NVS_Failed = 21, //!< 21
Alarm_AlarmMax = Alarm_NVS_Failed
Alarm_BufferOverflow = 22, //!< 22
Alarm_AlarmMax = Alarm_BufferOverflow
} __attribute__ ((__packed__)) alarm_code_t;
typedef struct {
+24
View File
@@ -1,5 +1,29 @@
## 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
Core:
+1 -1
View File
@@ -42,7 +42,7 @@
#else
#define GRBL_VERSION "1.1f"
#endif
#define GRBL_BUILD 20260811
#define GRBL_BUILD 20260813
#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.
// While in M3 laser mode also set spindle state and force a buffer sync
// 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) {
protocol_buffer_synchronize();
if(!plan_buffer_line(target, pl_data) &&
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);
}
#ifdef KINEMATICS_API
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.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;
}
@@ -1762,7 +1763,7 @@ FLASHMEM static uint32_t get_int (setting_id_t id)
break;
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;
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 },
#endif
{ 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_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
+2 -1
View File
@@ -611,7 +611,8 @@ typedef union {
m98_prescan_enable :1,
rotary_fix_enable :1,
revert_metric_conversion :1, // For rotary axes inch/min -> mm/min
unassigned :6;
set_rpm_0_during_hold :1,
unassigned :5;
};
} 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;
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))
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);
spindle->param->option.restore_rpm = Off;
}
return ok;
}
@@ -889,6 +891,7 @@ FLASHMEM void spindle_all_off (bool reset)
spindle->param->rpm = spindle->param->rpm_overridden = 0.0f;
spindle->param->state.value = 0;
spindle->param->option.restore_rpm = Off;
#ifdef GRBL_ESP32
spindle->esp32_off(spindle);
#else
+2 -1
View File
@@ -364,7 +364,8 @@ typedef struct spindle_param {
uint8_t ramp_up :1,
ramp_down :1,
ignore_delays :1,
override_disable :1;
override_disable :1,
restore_rpm :1;
} option;
spindle_ptrs_t *hal;
} 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.
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;
if(sys.override.spindle_stop.value) {
spindle_t *spindle = &restore_condition.spindle[restore_condition.spindle_num];
// Handles beginning of spindle stop
if(sys.override.spindle_stop.initiate) {
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);
}
} 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.
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;
}
}
@@ -595,6 +595,18 @@ FLASHMEM static void state_await_hold (uint_fast16_t rt_exec)
sys.flags.is_parking = false;
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:
break;
}
@@ -674,18 +686,19 @@ FLASHMEM static void state_await_resume (uint_fast16_t rt_exec)
break;
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)
gc_spindle_off();
sys.override.spindle_stop.value = 0; // Clear spindle stop override states
} 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);
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.
coolant_restore(restore_condition.coolant, settings.coolant.on_delay);
gc_coolant(restore_condition.coolant);