From 553c501f35d005bc92b8851548ecfe2d0327ae94 Mon Sep 17 00:00:00 2001 From: Terje Io Date: Thu, 13 Aug 2026 18:47:32 +0200 Subject: [PATCH] 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. --- README.md | 2 +- alarms.c | 3 ++- alarms.h | 5 +++-- changelog.md | 24 ++++++++++++++++++++++++ grbl.h | 2 +- motion_control.c | 8 +++++--- settings.c | 5 +++-- settings.h | 3 ++- spindle_control.c | 7 +++++-- spindle_control.h | 3 ++- state_machine.c | 31 ++++++++++++++++++++++--------- 11 files changed, 70 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 18a09e6..fbd76c8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/alarms.c b/alarms.c index 96962f7..15d0227 100644 --- a/alarms.c +++ b/alarms.c @@ -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 = { diff --git a/alarms.h b/alarms.h index ef36edc..155b9a5 100644 --- a/alarms.h +++ b/alarms.h @@ -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 { diff --git a/changelog.md b/changelog.md index 85ec772..cb4b195 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,29 @@ ## grblHAL changelog +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). + +--- + Build 20260811 Core: diff --git a/grbl.h b/grbl.h index d268a00..7a3b6db 100644 --- a/grbl.h +++ b/grbl.h @@ -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" diff --git a/motion_control.c b/motion_control.c index 1f91175..efcc954 100644 --- a/motion_control.c +++ b/motion_control.c @@ -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) { diff --git a/settings.c b/settings.c index 90ba97d..887e266 100644 --- a/settings.c +++ b/settings.c @@ -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 diff --git a/settings.h b/settings.h index b29e461..039db1d 100644 --- a/settings.h +++ b/settings.h @@ -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; diff --git a/spindle_control.c b/spindle_control.c index 99762f3..699729f 100644 --- a/spindle_control.c +++ b/spindle_control.c @@ -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 diff --git a/spindle_control.h b/spindle_control.h index 4dc6d89..ac29d82 100644 --- a/spindle_control.h +++ b/spindle_control.h @@ -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; diff --git a/state_machine.c b/state_machine.c index 4e4cb5a..78bef88 100644 --- a/state_machine.c +++ b/state_machine.c @@ -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);