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);