Added experimental support for M98 subroutines, internal subroutines are only supported for programs run from a local file system.

$700 controls whether they are scanned for internally in the main program (1) or always located externally (0).
If scanned for internally the program is run twice, initially in check mode to locate the subroutines before it is rewound and run in normal mode.
If stored externally the file P<macro number>.macro is run, <macro number> is the M98 P value.
This commit is contained in:
Terje Io
2026-02-02 22:25:31 +01:00
parent 08c778530c
commit c6aa574279
11 changed files with 225 additions and 58 deletions

View File

@@ -1,6 +1,6 @@
## grblHAL ## ## grblHAL ##
Latest build date is 20260125, see the [changelog](changelog.md) for details. Latest build date is 20260202, 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.
@@ -89,4 +89,4 @@ G/M-codes not supported by [legacy Grbl](https://github.com/gnea/grbl/wiki) are
Some [plugins](https://github.com/grblHAL/plugins) implements additional M-codes. Some [plugins](https://github.com/grblHAL/plugins) implements additional M-codes.
--- ---
20260125 20260202

View File

@@ -1,5 +1,32 @@
## grblHAL changelog ## grblHAL changelog
<a name="20260202">20260202
Core:
* Added _experimental_ support for M98 subroutines, internal subroutines are only supported for programs run from a local file system.
`$700` controls whether they are scanned for internally in the main program \(1\) or always located externally \(0\).
If scanned for internally the program is run twice, initially in check mode to locate the subroutines before it is rewound and run in normal mode.
If stored externally the file _P\<macro number\>.macro_ is run, _\<macro number\>_ is the `M98` `P` value.
Ref. discussion [789](https://github.com/grblHAL/core/discussions/789).
> [!NOTE]
> If a subroutine is not found in the main program it is assumed to be an external routine.
> Internal subroutines must be located _after_ the main program part which has to be terminated by `M2` or `M30`.
Drivers:
* ESP32, S3: fix for compilation error when USB serial comms is enabled.
* STM32F1xx: fix for copy/paste error in SVN board map.
Plugins:
* Spindle, offset: updated for core change.
* Keypad, I2C display interface: updated for core change.
---
<a name="20260128">20260131 <a name="20260128">20260131
Plugins: Plugins:

184
gcode.c
View File

@@ -74,10 +74,11 @@ typedef union {
G8 :1, //!< [G43,G43.1,G49] Tool length offset G8 :1, //!< [G43,G43.1,G49] Tool length offset
G10 :1, //!< [G98,G99] Return mode in canned cycles G10 :1, //!< [G98,G99] Return mode in canned cycles
G11 :1, //!< [G50,G51] Scaling G11 :1, //!< [G50,G51] Scaling
G12 :1, //!< [G54,G55,G56,G57,G58,G59,G59.1,G59.2,G59.3] Coordinate system selection G12 :1, //!< [G54,G55,G56,G57,G58,G59,G59.1,G59.2,G59.3] Coordinate system selection (14)
G13 :1, //!< [G61] Control mode G13 :1, //!< [G61] Control mode (15)
G14 :1, //!< [G96,G97] Spindle Speed Mode G14 :1, //!< [G96,G97] Spindle Speed Mode (13)
G15 :1, //!< [G7,G8] Lathe Diameter Mode G15 :1, //!< [G7,G8] Lathe Diameter Mode
// G16 :1, //!< [G66,G67] Modal macro call (12)
M4 :1, //!< [M0,M1,M2,M30] Stopping M4 :1, //!< [M0,M1,M2,M30] Stopping
M5 :1, //!< [M62,M63,M64,M65,M66,M67,M68] Aux I/O M5 :1, //!< [M62,M63,M64,M65,M66,M67,M68] Aux I/O
@@ -116,11 +117,19 @@ typedef union {
}; };
} ijk_words_t; } ijk_words_t;
typedef struct m98_macro {
macro_id_t id;
vfs_file_t *file;
size_t pos;
struct m98_macro *next;
} m98_macro_t;
// Declare gc extern struct // Declare gc extern struct
DCRAM parser_state_t gc_state; DCRAM parser_state_t gc_state;
#define RETURN(status) return gc_at_exit(status); #define RETURN(status) return gc_at_exit(status);
m98_macro_t *m98_macros = NULL;
static output_command_t *output_commands = NULL; // Linked list static output_command_t *output_commands = NULL; // Linked list
static scale_factor_t scale_factor = { static scale_factor_t scale_factor = {
.ijk[X_AXIS] = 1.0f, .ijk[X_AXIS] = 1.0f,
@@ -604,6 +613,68 @@ bool gc_modal_state_restore (gc_modal_snapshot_t *snapshot)
#endif // NGC_PARAMETERS_ENABLE #endif // NGC_PARAMETERS_ENABLE
static m98_macro_t *macro_find (macro_id_t id)
{
m98_macro_t *sub;
if((sub = m98_macros)) do {
if(sub->id == id)
break;
} while((sub = sub->next));
return sub;
}
size_t gc_macro_get_pos (macro_id_t id, vfs_file_t *file)
{
m98_macro_t *sub = macro_find(id);
return sub && sub->file == file ? sub->pos : 0;
}
/*
bool gc_macros_validate (void)
{
bool ok = true;
m98_macro_t *sub;
if((sub = m98_macros)) do {
ok = sub->pos != 0;
} while(ok && (sub = sub->next));
return ok;
}
*/
static void macros_clear (void)
{
m98_macro_t *next;
if(m98_macros) do {
next = m98_macros->next;
free(m98_macros);
} while((m98_macros = next));
}
static status_code_t macro_add (macro_id_t id, vfs_file_t *file)
{
m98_macro_t *sub = macro_find(id);
if(sub == NULL && (sub = calloc(sizeof(m98_macro_t), 1))) {
sub->id = id;
sub->file = file;
if(m98_macros) {
m98_macro_t *add = m98_macros;
while(add->next)
add = add->next;
add->next = sub;
} else
m98_macros = sub;
}
return sub ? Status_OK : Status_FlowControlOutOfMemory;
}
static status_code_t macro_call (macro_id_t macro, parameter_words_t args, uint8_t repeats) static status_code_t macro_call (macro_id_t macro, parameter_words_t args, uint8_t repeats)
{ {
#if NGC_PARAMETERS_ENABLE #if NGC_PARAMETERS_ENABLE
@@ -622,11 +693,14 @@ static status_code_t macro_call (macro_id_t macro, parameter_words_t args, uint8
static status_code_t gc_at_exit (status_code_t status) static status_code_t gc_at_exit (status_code_t status)
{ {
if(status != Status_OK) { if(!(status == Status_OK || status == Status_Handled)) {
// Clear any pending output commands // Clear any pending output commands
gc_clear_output_commands(output_commands); gc_clear_output_commands(output_commands);
// Clear any registered M98 macros
macros_clear();
#if NGC_PARAMETERS_ENABLE #if NGC_PARAMETERS_ENABLE
// Clear the G66 arguments stack // Clear the G66 arguments stack
@@ -926,7 +1000,7 @@ char *gc_normalize_block (char *block, status_code_t *status, char **message)
break; break;
case ')': case ')':
if(comment && !gc_state.skip_blocks) { if(comment && !(gc_state.skip_blocks || state_get() == STATE_CHECK_MODE)) {
*s1 = '\0'; *s1 = '\0';
if(!hal.driver_cap.no_gcode_message_handling) { if(!hal.driver_cap.no_gcode_message_handling) {
@@ -1076,8 +1150,11 @@ status_code_t gc_execute_block (char *block)
else if(!(gc_state.file_run = fs_changed)) { else if(!(gc_state.file_run = fs_changed)) {
protocol_buffer_synchronize(); // Empty planner buffer protocol_buffer_synchronize(); // Empty planner buffer
grbl.report.feedback_message(Message_ProgramEnd); grbl.report.feedback_message(Message_ProgramEnd);
if(grbl.on_program_completed) if(grbl.on_program_completed) {
grbl.on_program_completed(ProgramFlow_EndPercent, state_get() == STATE_CHECK_MODE); bool check_mode = state_get() == STATE_CHECK_MODE;
grbl.on_program_completed(ProgramFlow_EndPercent, check_mode);
gc_state.file_stream = !check_mode;
}
} }
} else } else
gc_state.file_run = !gc_state.file_run; gc_state.file_run = !gc_state.file_run;
@@ -1619,7 +1696,7 @@ status_code_t gc_execute_block (char *block)
case 66: case 66:
if(!ioports_can_do().wait_on_input || (ioports_unclaimed(Port_Digital, Port_Input) == 0 && if(!ioports_can_do().wait_on_input || (ioports_unclaimed(Port_Digital, Port_Input) == 0 &&
ioports_unclaimed(Port_Analog, Port_Input) == 0)) ioports_unclaimed(Port_Analog, Port_Input) == 0))
RETURN(Status_GcodeUnsupportedCommand); // [Unsupported M command] RETURN(Status_GcodeUnsupportedCommand); // [Unsupported M command]
word_bit.modal_group.M5 = On; word_bit.modal_group.M5 = On;
port_command = (io_mcode_t)int_value; port_command = (io_mcode_t)int_value;
@@ -1640,11 +1717,18 @@ status_code_t gc_execute_block (char *block)
break; break;
#endif #endif
case 98:
if(mantissa != 0 || grbl.on_macro_execute == NULL)
RETURN(Status_GcodeUnsupportedCommand);
word_bit.modal_group.M4 = On;
gc_block.non_modal_command = NonModal_MacroCall2;
break;
case 99: case 99:
if(mantissa != 0 || !(!!hal.stream.file || !!grbl.on_macro_return))
RETURN(Status_GcodeUnsupportedCommand);
word_bit.modal_group.M4 = On; word_bit.modal_group.M4 = On;
gc_block.modal.program_flow = ProgramFlow_Return; gc_block.modal.program_flow = ProgramFlow_Return;
if(grbl.on_macro_return == NULL)
RETURN(Status_GcodeUnsupportedCommand);
break; break;
default: default:
@@ -1754,10 +1838,20 @@ status_code_t gc_execute_block (char *block)
break; break;
case 'O': case 'O':
if (mantissa > 0) if(mantissa > 0)
RETURN(Status_GcodeCommandValueNotInteger); RETURN(Status_GcodeCommandValueNotInteger);
word_bit.parameter.o = On; {
gc_block.values.o = int_value; m98_macro_t *macro;
if(hal.stream.state.m98_macro_prescan && (macro = macro_find((macro_id_t)int_value))) {
macro->file = hal.stream.file;
macro->pos = vfs_tell(macro->file);
RETURN(Status_OK);
} else {
word_bit.parameter.o = On;
gc_block.values.o = int_value;
}
}
break; break;
case 'P': // NOTE: For certain commands, P value must be an integer, but none of these commands are supported. case 'P': // NOTE: For certain commands, P value must be an integer, but none of these commands are supported.
@@ -1939,9 +2033,11 @@ status_code_t gc_execute_block (char *block)
// [0. Non-specific/common error-checks and miscellaneous setup]: // [0. Non-specific/common error-checks and miscellaneous setup]:
if(word_bit.modal_group.G0 && if((word_bit.modal_group.G0 &&
(gc_block.non_modal_command == Modal_MacroCall || (gc_block.non_modal_command == Modal_MacroCall ||
gc_block.non_modal_command == NonModal_MacroCall)) { gc_block.non_modal_command == NonModal_MacroCall)) ||
(word_bit.modal_group.M4 &&
gc_block.non_modal_command == NonModal_MacroCall2)) {
if(!gc_block.words.p) if(!gc_block.words.p)
RETURN(Status_GcodeValueWordMissing); // [P word missing] RETURN(Status_GcodeValueWordMissing); // [P word missing]
@@ -1955,27 +2051,30 @@ status_code_t gc_execute_block (char *block)
#if NGC_PARAMETERS_ENABLE #if NGC_PARAMETERS_ENABLE
// Remove axis and ijk words flags since values are to be passed unmodified. if(gc_block.non_modal_command != NonModal_MacroCall2) {
axis_words.mask = ijk_words.mask = 0;
if(gc_block.non_modal_command == NonModal_MacroCall) { // Remove axis and ijk words flags since values are to be passed unmodified.
// TODO: add context for local storage? axis_words.mask = ijk_words.mask = 0;
if(!ngc_call_push(&gc_state + ngc_call_level()))
RETURN(Status_FlowControlStackOverflow); // [Call level too deep] if(gc_block.non_modal_command == NonModal_MacroCall) {
gc_block.g65_words.mask = macro_arguments_push(&gc_block.values, gc_block.words).mask; // TODO: add context for local storage?
gc_block.words.mask &= ~gc_block.g65_words.mask; // Remove G65 arguments if(!ngc_call_push(&gc_state + ngc_call_level()))
} else { // G66 RETURN(Status_FlowControlStackOverflow); // [Call level too deep]
g66_arguments_t *args; gc_block.g65_words.mask = macro_arguments_push(&gc_block.values, gc_block.words).mask;
// if(gc_state.g66_args && gc_state.g66_args->call_level == ngc_call_level()) gc_block.words.mask &= ~gc_block.g65_words.mask; // Remove G65 arguments
// RETURN(cannot have nested g66 calls on the same call level?); } else { // G66
if((args = malloc(sizeof(g66_arguments_t)))) { g66_arguments_t *args;
args->prev = gc_state.g66_args; // if(gc_state.g66_args && gc_state.g66_args->call_level == ngc_call_level())
gc_state.g66_args = args; // RETURN(cannot have nested g66 calls on the same call level?);
gc_state.g66_args->call_level = ngc_call_level(); if((args = malloc(sizeof(g66_arguments_t)))) {
gc_state.g66_args->words.mask = gc_block.words.mask; args->prev = gc_state.g66_args;
memcpy(&gc_state.g66_args->values, &gc_block.values, sizeof(gc_values_t)); gc_state.g66_args = args;
gc_state.g66_args->call_level = ngc_call_level();
gc_state.g66_args->words.mask = gc_block.words.mask;
memcpy(&gc_state.g66_args->values, &gc_block.values, sizeof(gc_values_t));
}
RETURN(args ? Status_OK : Status_FlowControlStackOverflow);
} }
RETURN(args ? Status_OK : Status_FlowControlStackOverflow);
} }
#endif // NGC_PARAMETERS_ENABLE #endif // NGC_PARAMETERS_ENABLE
} }
@@ -4092,6 +4191,14 @@ status_code_t gc_execute_block (char *block)
break; break;
#endif #endif
case NonModal_MacroCall2:
if(check_mode) {
RETURN(macro_add((macro_id_t)gc_block.values.p, hal.stream.file));
} else {
RETURN(macro_call((macro_id_t)gc_block.values.p, (parameter_words_t){ .$ = On}, gc_block.values.l));
}
break;
case NonModal_SetCoordinateOffset: // G92 case NonModal_SetCoordinateOffset: // G92
add_offset((coord_data_t *)gc_block.values.xyz); add_offset((coord_data_t *)gc_block.values.xyz);
gc_state.g92_offset_applied = true; // TODO: check for all zero? gc_state.g92_offset_applied = true; // TODO: check for all zero?
@@ -4315,6 +4422,8 @@ status_code_t gc_execute_block (char *block)
if(gc_state.modal.program_flow == ProgramFlow_Return) { if(gc_state.modal.program_flow == ProgramFlow_Return) {
if(grbl.on_macro_return) if(grbl.on_macro_return)
grbl.on_macro_return(); grbl.on_macro_return();
else if(grbl.on_program_completed)
grbl.on_program_completed(gc_state.modal.program_flow, check_mode);
} else if(gc_state.modal.program_flow == ProgramFlow_Paused || gc_block.modal.program_flow == ProgramFlow_OptionalStop || gc_block.modal.program_flow == ProgramFlow_CompletedM60 || sys.flags.single_block) { } else if(gc_state.modal.program_flow == ProgramFlow_Paused || gc_block.modal.program_flow == ProgramFlow_OptionalStop || gc_block.modal.program_flow == ProgramFlow_CompletedM60 || sys.flags.single_block) {
if(!check_mode) { if(!check_mode) {
if(gc_block.modal.program_flow == ProgramFlow_CompletedM60 && hal.pallet_shuttle) if(gc_block.modal.program_flow == ProgramFlow_CompletedM60 && hal.pallet_shuttle)
@@ -4401,14 +4510,15 @@ status_code_t gc_execute_block (char *block)
if(grbl.on_program_completed) if(grbl.on_program_completed)
grbl.on_program_completed(gc_state.modal.program_flow, check_mode); grbl.on_program_completed(gc_state.modal.program_flow, check_mode);
// Clear any pending output commands // Clear any pending output commands etc...
gc_clear_output_commands(output_commands); gc_at_exit(hal.stream.state.m98_macro_prescan ? Status_Handled : Status_UserException);
#if NGC_PARAMETERS_ENABLE #if NGC_PARAMETERS_ENABLE
ngc_modal_state_invalidate(); ngc_modal_state_invalidate();
#endif #endif
grbl.report.feedback_message(Message_ProgramEnd); if(!check_mode || !settings.flags.m98_prescan_enable)
grbl.report.feedback_message(Message_ProgramEnd);
} }
gc_state.modal.program_flow = ProgramFlow_Running; // Reset program flow. gc_state.modal.program_flow = ProgramFlow_Running; // Reset program flow.
} }

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
@@ -28,6 +28,7 @@
#include "coolant_control.h" #include "coolant_control.h"
#include "spindle_control.h" #include "spindle_control.h"
#include "errors.h" #include "errors.h"
#include "vfs.h"
#define MAX_OFFSET_ENTRIES 4 // must be a power of 2 #define MAX_OFFSET_ENTRIES 4 // must be a power of 2
@@ -60,6 +61,7 @@ typedef enum {
Modal_MacroCall = 66, //!< 66 - G66 Modal_MacroCall = 66, //!< 66 - G66
Modal_MacroEnd = 67, //!< 67 - G67 Modal_MacroEnd = 67, //!< 67 - G67
NonModal_SetCoordinateOffset = 92, //!< 92 - G92 NonModal_SetCoordinateOffset = 92, //!< 92 - G92
NonModal_MacroCall2 = 98, //!< 98 - M98
NonModal_ResetCoordinateOffset = 102, //!< 102 - G92.1 NonModal_ResetCoordinateOffset = 102, //!< 102 - G92.1
NonModal_ClearCoordinateOffset = 112, //!< 112 - G92.2 NonModal_ClearCoordinateOffset = 112, //!< 112 - G92.2
#if ENABLE_ACCELERATION_PROFILES #if ENABLE_ACCELERATION_PROFILES
@@ -741,6 +743,7 @@ void gc_coolant (coolant_state_t state);
void gc_set_tool_offset (tool_offset_mode_t mode, uint_fast8_t idx, int32_t offset); void gc_set_tool_offset (tool_offset_mode_t mode, uint_fast8_t idx, int32_t offset);
plane_t *gc_get_plane_data (plane_t *plane, plane_select_t select); plane_t *gc_get_plane_data (plane_t *plane, plane_select_t select);
axes_signals_t gc_claim_axis_words (parser_block_t *gc_block, axes_signals_t validate); axes_signals_t gc_claim_axis_words (parser_block_t *gc_block, axes_signals_t validate);
size_t gc_macro_get_pos (macro_id_t id, vfs_file_t *file);
#if NGC_PARAMETERS_ENABLE #if NGC_PARAMETERS_ENABLE
parameter_words_t gc_get_g65_arguments (void); parameter_words_t gc_get_g65_arguments (void);

2
grbl.h
View File

@@ -42,7 +42,7 @@
#else #else
#define GRBL_VERSION "1.1f" #define GRBL_VERSION "1.1f"
#endif #endif
#define GRBL_BUILD 20260126 #define GRBL_BUILD 20260202
#define GRBL_URL "https://github.com/grblHAL" #define GRBL_URL "https://github.com/grblHAL"

View File

@@ -766,8 +766,12 @@ status_code_t ngc_flowctrl (uint32_t o_label, char *line, uint_fast8_t *pos, boo
*skip = false; *skip = false;
if(settings.flags.ngc_debug_out) if(settings.flags.ngc_debug_out)
report_message(line, Message_Plain); report_message(line, Message_Plain);
} else } else {
*skip = skip_sub || (stack_idx >= 0 && stack[stack_idx].skip); *skip = skip_sub || (stack_idx >= 0 && stack[stack_idx].skip);
// char buf[200];
// sprintf(buf, "%d %d %d %s", *skip, stack_idx, stack[stack_idx].operation, line);
// report_message(buf, Message_Plain);
}
return status; return status;
} }

View File

@@ -916,6 +916,13 @@ static status_code_t set_enable_legacy_rt_commands (setting_id_t id, uint_fast16
return Status_OK; return Status_OK;
} }
static status_code_t set_suboptions (setting_id_t id, uint_fast16_t int_value)
{
settings.flags.m98_prescan_enable = int_value != 0;
return Status_OK;
}
#if !LATHE_UVW_OPTION #if !LATHE_UVW_OPTION
static status_code_t set_mode (setting_id_t id, uint_fast16_t int_value) static status_code_t set_mode (setting_id_t id, uint_fast16_t int_value)
@@ -1693,6 +1700,10 @@ FLASHMEM static uint32_t get_int (setting_id_t id)
((!settings.flags.keep_feed_override_on_reset) << 3); ((!settings.flags.keep_feed_override_on_reset) << 3);
break; break;
case Setting_SubroutineOptions:
value = settings.flags.m98_prescan_enable;
break;
default: default:
break; break;
} }
@@ -2005,6 +2016,7 @@ FLASHMEM static bool is_setting_available (const setting_detail_t *setting, uint
break; break;
case Setting_FSOptions: case Setting_FSOptions:
case Setting_SubroutineOptions:
available = hal.driver_cap.sd_card || hal.driver_cap.littlefs; available = hal.driver_cap.sd_card || hal.driver_cap.littlefs;
break; break;
@@ -2204,6 +2216,7 @@ PROGMEM static const setting_detail_t setting_detail[] = {
{ Setting_MotorFaultsInvert, Group_Stepper, "Invert motor fault inputs", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.motor_fault_invert, NULL, is_setting_available }, { Setting_MotorFaultsInvert, Group_Stepper, "Invert motor fault inputs", NULL, Format_AxisMask, NULL, NULL, NULL, Setting_IsExtended, &settings.motor_fault_invert, NULL, is_setting_available },
{ Setting_ResetActions, Group_General, "Reset actions", NULL, Format_Bitfield, "Clear homed status if position was lost,Clear offsets (except G92),Clear rapids override,Clear feed override", NULL, NULL, Setting_IsExtendedFn, set_reset_actions, get_int, NULL }, { Setting_ResetActions, Group_General, "Reset actions", NULL, Format_Bitfield, "Clear homed status if position was lost,Clear offsets (except G92),Clear rapids override,Clear feed override", NULL, NULL, Setting_IsExtendedFn, set_reset_actions, get_int, NULL },
{ Setting_StepperEnableDelay, Group_Stepper, "Stepper enable delay", "ms", Format_Int16, "##0", NULL, "500", Setting_IsExtended, &settings.stepper_enable_delay, NULL, NULL }, { Setting_StepperEnableDelay, Group_Stepper, "Stepper enable delay", "ms", Format_Int16, "##0", NULL, "500", Setting_IsExtended, &settings.stepper_enable_delay, NULL, NULL },
{ Setting_SubroutineOptions, Group_General, "Subroutine options", NULL, Format_Bitfield, "Prescan for internal M98 subroutines", NULL, NULL, Setting_IsExtendedFn, set_suboptions, get_int, is_setting_available }
}; };
PROGMEM static const setting_descr_t setting_descr[] = { PROGMEM static const setting_descr_t setting_descr[] = {
@@ -2410,7 +2423,8 @@ PROGMEM static const setting_descr_t setting_descr[] = {
{ Setting_HomePinsInvertMask, "Inverts the axis home input signals." }, { Setting_HomePinsInvertMask, "Inverts the axis home input signals." },
{ Setting_CoolantOnDelay, "Delay to allow coolant to start. 0 or 0.5 - 20s." }, { Setting_CoolantOnDelay, "Delay to allow coolant to start. 0 or 0.5 - 20s." },
{ Setting_ResetActions, "Controls actions taken on a soft reset." }, { Setting_ResetActions, "Controls actions taken on a soft reset." },
{ Setting_StepperEnableDelay, "Delay from stepper enable to first step output. The driver typically adds ~2ms to this." } { Setting_StepperEnableDelay, "Delay from stepper enable to first step output. The driver typically adds ~2ms to this." },
// { Setting_SubroutineOptions, "Enable prescan for internal M98 subroutines." }
/* /*
{ Setting_MotorWarningsEnable, "Motor warning enable" }, { Setting_MotorWarningsEnable, "Motor warning enable" },
{ Setting_MotorWarningsInvert, "Invert motor warning inputs" }, { Setting_MotorWarningsInvert, "Invert motor warning inputs" },

View File

@@ -435,7 +435,6 @@ typedef enum {
Setting_Stepper4 = 654, Setting_Stepper4 = 654,
Setting_Stepper5 = 655, Setting_Stepper5 = 655,
Setting_Stepper6 = 656, Setting_Stepper6 = 656,
Setting_Stepper7 = 657,
Setting_Stepper8 = 658, Setting_Stepper8 = 658,
Setting_Stepper9 = 659, Setting_Stepper9 = 659,
Setting_Stepper10 = 660, Setting_Stepper10 = 660,
@@ -464,6 +463,8 @@ typedef enum {
Setting_THC_FeedFactor = 682, Setting_THC_FeedFactor = 682,
// 683 - 689 - reserved for Sienci // 683 - 689 - reserved for Sienci
Setting_SubroutineOptions = 700,
Setting_SpindlePWMOptions1 = 709, Setting_SpindlePWMOptions1 = 709,
Setting_SpindleInvertMask1 = 716, Setting_SpindleInvertMask1 = 716,
@@ -605,7 +606,8 @@ typedef union {
tool_persistent :1, tool_persistent :1,
keep_rapids_override_on_reset :1, keep_rapids_override_on_reset :1,
keep_feed_override_on_reset :1, keep_feed_override_on_reset :1,
unassigned :9; m98_prescan_enable :1,
unassigned :8;
}; };
} settingflags_t; } settingflags_t;

View File

@@ -3,7 +3,7 @@
Part of grblHAL Part of grblHAL
Copyright (c) 2019-2025 Terje Io Copyright (c) 2019-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -280,13 +280,14 @@ typedef union {
typedef union { typedef union {
uint8_t value; uint8_t value;
struct { struct {
uint8_t webui_connected :1, uint8_t webui_connected :1,
is_usb :1, is_usb :1,
linestate_event :1, //!< Set when driver supports on_linestate_changed event. linestate_event :1, //!< Set when driver supports on_linestate_changed event.
passthru :1, //!< Set when stream is in passthru mode. passthru :1, //!< Set when stream is in passthru mode.
utf8 :1, //!< Set when stream is in UTF8 mode. utf8 :1, //!< Set when stream is in UTF8 mode.
eof :1, //!< Set when a file stream reaches end-of-file. eof :1, //!< Set when a file stream reaches end-of-file.
unused :2; m98_macro_prescan :1, //!< Set when prescanning gcode for M98 macro definitions.
unused :1;
}; };
} io_stream_state_t; } io_stream_state_t;

6
vfs.c
View File

@@ -5,7 +5,7 @@
Part of grblHAL Part of grblHAL
Copyright (c) 2022-2025 Terje Io Copyright (c) 2022-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -267,7 +267,7 @@ vfs_file_t *vfs_open (const char *filename, const char *mode)
if(mount && (file = mount->vfs->fopen(get_filename(mount, filename), mode))) { if(mount && (file = mount->vfs->fopen(get_filename(mount, filename), mode))) {
file->fs = mount->vfs; file->fs = mount->vfs;
file->update = !mount->mode.hidden && !!strchr(mode, 'w'); file->status.update = !mount->mode.hidden && !!strchr(mode, 'w');
} }
return file; return file;
@@ -279,7 +279,7 @@ void vfs_close (vfs_file_t *file)
((vfs_t *)(file->fs))->fclose(file); ((vfs_t *)(file->fs))->fclose(file);
if(file->update && vfs.on_fs_changed) if(file->status.update && vfs.on_fs_changed)
vfs.on_fs_changed((vfs_t *)file->fs); vfs.on_fs_changed((vfs_t *)file->fs);
} }

10
vfs.h
View File

@@ -5,7 +5,7 @@
Part of grblHAL Part of grblHAL
Copyright (c) 2022-2025 Terje Io Copyright (c) 2022-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -78,10 +78,16 @@ typedef struct {
#endif #endif
} vfs_stat_t; } vfs_stat_t;
typedef struct {
uint8_t update :1,
is_temporary :1,
unused :6;
} vfs_file_status_t;
typedef struct { typedef struct {
const void *fs; const void *fs;
size_t size; size_t size;
bool update; vfs_file_status_t status;
uint8_t handle __attribute__ ((aligned (4))); // first byte of file handle structure uint8_t handle __attribute__ ((aligned (4))); // first byte of file handle structure
} vfs_file_t; } vfs_file_t;