diff --git a/CMakeLists.txt b/CMakeLists.txt
index b4f695c..1075d5d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,7 @@ target_sources(grbl INTERFACE
${CMAKE_CURRENT_LIST_DIR}/utf8.c
${CMAKE_CURRENT_LIST_DIR}/vfs.c
${CMAKE_CURRENT_LIST_DIR}/canbus.c
+ ${CMAKE_CURRENT_LIST_DIR}/encoders.c
${CMAKE_CURRENT_LIST_DIR}/pid.c
${CMAKE_CURRENT_LIST_DIR}/fs_device.c
${CMAKE_CURRENT_LIST_DIR}/kinematics/corexy.c
diff --git a/README.md b/README.md
index 06d2cb6..430d05e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
## grblHAL ##
-Latest build date is 20260218, see the [changelog](changelog.md) for details.
+Latest build date is 20260225, 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/changelog.md b/changelog.md
index cce4f17..9fd66fd 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,10 +1,55 @@
## grblHAL changelog
+Build 20260225
+
+Core:
+
+* Fix incorrect behaviour when a tool change using the built-in workflow is aborted, the selected tool was set as the current tool.
+
+* Clarification: `G66` macros calls are not run on their first invocation and will only be run on subsequent blocks containing axis words for `G0` or `G1` motion.
+Subsequent blocks may contain either the `G0` or `G1` command word, if not their modal state will be used. The macro will be called after the motion has been sent to the planner.
+
+* Added support for `G66.1` macro call. Unlike `G66` this is run on the first invocation and for each subsequent block containing parameter word\(s\) used in the first invocation.
+Parameter values changed in subsequent blocks are "sticky", that is they will keep their value for subsequent blocks until changed again.
+
+> [!NOTE]
+> `G66` and `G66.1` behaviour may change in later builds since I have not found any definite specification, it seems that there are different implementations between controllers.
+> grbHAL aims to adopt the Fanuc behaviour, but this has not been verified.
+
+* Changed behaviour of `G50` and `G51` \(and the `G48` and `G49` shortcuts\) controlling feed rate and spindle RPM overrides.
+When used with `P0` to turn off the feed rate and/or the spindle RPM will be reverted to their programmed values.
+The override values can still be changed and any changed value will be output in the real time report.
+When turned back on the current override value\(s\) will be reapplied.
+
+* Added support for `G10L0`, reload file based tool table. Can only be used when no tool (tool 0) is in the spindle.
+
+* Fixed buggy handling of `G43` - apply tool offset from tool table.
+
+* Refactored the encoder HAL/API to make it more flexible and as a first step to support rigid tapping.
+For programmers: encoders are now registered with the core allowing them to be added from both drivers and plugins.
+Plugins can now claim encoders, and code has been added to the core to make it easy for drivers and plugins to bind a slow encoder to interrupt capable auxiliary inputs.
+> [!NOTE]
+> The new HAL/API will be extended as needed later.
+
+Drivers:
+
+* iMXRT1062, STM32F4xx, STM32F7xx: updated for the new encoder HAL/API.
+
+* RP2040: fixed typos affecting limit switch inversion for A+ axes.
+
+Plugins:
+
+* Encoder: updated to use the new encoder HAL/API.
+
+* Misc, Tool table: added support for `G10L0`, reload tool table.
+
+---
+
Build 20260218
Core:
-* Changed `$pinstates` command output, now reports pin number instead of pin id.
+* Changed `$pinstate` command output, now reports pin number instead of pin id.
* For developers: added API call that returns Modbus stream handlers.
diff --git a/core_handlers.h b/core_handlers.h
index 48840a6..7e6aac5 100644
--- a/core_handlers.h
+++ b/core_handlers.h
@@ -143,6 +143,7 @@ typedef tool_table_entry_t *(*get_tool_ptr)(tool_id_t tool_id);
typedef tool_table_entry_t *(*get_tool_by_idx_ptr)(uint32_t idx);
typedef bool (*set_tool_data_ptr)(tool_data_t *tool_data);
typedef bool (*clear_tool_data_ptr)(void);
+typedef status_code_t (*reload_tool_data_ptr)(void);
typedef struct {
uint32_t n_tools;
@@ -150,6 +151,7 @@ typedef struct {
get_tool_by_idx_ptr get_tool_by_idx;
set_tool_data_ptr set_tool;
clear_tool_data_ptr clear;
+ reload_tool_data_ptr reload;
} tool_table_t;
/*****************
diff --git a/driver_opts.h b/driver_opts.h
index 4e899cd..f5ba56e 100644
--- a/driver_opts.h
+++ b/driver_opts.h
@@ -5,7 +5,7 @@
Part of grblHAL
- Copyright (c) 2020-2025 Terje Io
+ Copyright (c) 2020-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -450,12 +450,18 @@
#endif
#endif
+#ifndef ENCODER_ENABLE
+#define ENCODER_ENABLE 0
+#endif
+
#ifndef QEI_ENABLE
+#if ENCODER_ENABLE
+#define QEI_ENABLE 1
+#else
#define QEI_ENABLE 0
#endif
-#ifndef QEI_SELECT_ENABLE
-#define QEI_SELECT_ENABLE 0
#endif
+
#ifndef ODOMETER_ENABLE
#define ODOMETER_ENABLE 0
#endif
diff --git a/driver_opts2.h b/driver_opts2.h
index 4988c0b..5bc193a 100644
--- a/driver_opts2.h
+++ b/driver_opts2.h
@@ -5,7 +5,7 @@
Part of grblHAL
- Copyright (c) 2024-2025 Terje Io
+ Copyright (c) 2024-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -43,6 +43,10 @@
#warning "Selected spindle 1 is not supported!"
#endif
+#if ENCODER_ENABLE > 0 && !(defined(QEI_A_PIN) && defined(QEI_B_PIN))
+#warning "ENCODER_ENABLE requires encoder input pins A and B to be defined!"
+#endif
+
#endif
#if MPG_ENABLE == 1 && !defined(MPG_MODE_PIN)
diff --git a/encoders.c b/encoders.c
new file mode 100644
index 0000000..39c55ec
--- /dev/null
+++ b/encoders.c
@@ -0,0 +1,79 @@
+/*
+ encoders.c - quadrature encoder interface (API)
+
+ Part of grblHAL
+
+ Copyright (c) 2026 Terje Io
+
+ grblHAL is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ grblHAL is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with grblHAL. If not, see .
+*/
+
+#include
+#include
+
+#include "hal.h"
+#include "encoders.h"
+
+static struct encoders {
+ encoder_t *encoder;
+ struct encoders *next;
+} *encoders = NULL;
+
+static uint8_t n_encoders = 0;
+
+typedef bool (*encoder_enumerate_callback_ptr)(encoder_t *properties, void *data);
+
+static void dummy_event_handler (encoder_t *encoder, encoder_event_t *events)
+{
+ UNUSED(encoder);
+
+ events->value = 0;
+}
+
+void encoder_register (encoder_t *encoder)
+{
+ struct encoders *add, *last;
+
+ if((add = malloc(sizeof(struct encoders)))) {
+
+ add->next = NULL;
+ add->encoder = encoder;
+
+ if((last = encoders)) {
+ while(last->next)
+ last = last->next;
+ last->next = add;
+ } else
+ encoders = add;
+
+ n_encoders++;
+ }
+}
+
+bool encoders_enumerate (encoder_enumerate_callback_ptr callback, void *data)
+{
+ bool ok = false;
+ struct encoders *encoder = encoders;
+
+ if(encoder) do {
+ ok = callback(encoder->encoder, data);
+ } while(!ok && (encoder = encoder->next));
+
+ return ok;
+}
+
+uint8_t encoders_get_count (void)
+{
+ return n_encoders;
+}
diff --git a/encoders.h b/encoders.h
new file mode 100644
index 0000000..509ebeb
--- /dev/null
+++ b/encoders.h
@@ -0,0 +1,318 @@
+/*
+ encoders.c - quadrature encoders interface (API)
+
+ Part of grblHAL
+
+ Copyright (c) 2026 Terje Io
+
+ grblHAL is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ grblHAL is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with grblHAL. If not, see .
+*/
+
+#ifndef _ENCODERS_H_
+#define _ENCODERS_H_
+
+#include "plugins.h"
+
+// Quadrature encoder interface
+
+typedef union {
+ uint8_t value;
+ uint8_t events;
+ struct {
+ uint8_t position_changed :1,
+ direction_changed :1,
+ click :1,
+ dbl_click :1,
+ long_click :1,
+ index_pulse :1,
+ unused :2;
+ };
+} encoder_event_t;
+
+typedef union {
+ uint8_t value;
+ uint8_t mask;
+ struct {
+ uint8_t bidirectional :1,
+ select :1,
+ index :1,
+ spindle_rpm :1,
+ spindle_pos :1,
+ unused :3;
+ };
+} encoder_caps_t;
+
+typedef struct {
+ uint32_t vel_timeout;
+ uint32_t dbl_click_window; //!< ms.
+} encoder_cfg_t;
+
+typedef struct {
+ int32_t position;
+ uint32_t velocity;
+} encoder_data_t;
+
+struct encoder;
+typedef struct encoder encoder_t;
+
+/*! \brief Pointer to callback function to receive encoder events.
+\param encoder pointer to a \a encoder_t struct.
+\param events pointer to a \a encoder_event_t struct.
+\param context pointer to the context passed to the encoders claim function.
+*/
+typedef void (*encoder_on_event_ptr)(encoder_t *encoder, encoder_event_t *events, void *context);
+
+/*! \brief Pointer to function for resetting encoder data.
+\param encoder pointer to a \a encoder_t struct.
+*/
+typedef void (*encoder_reset_ptr)(encoder_t *encoder);
+
+/*! \brief Pointer to function for claiming an encoder.
+\param event_handler pointer to to the event handler callback.
+\param context pointer to the context to be passed to event handler.
+\returns \a true when claim was successful, \a false to otherwise.
+*/
+typedef bool (*encoder_claim_ptr)(encoder_on_event_ptr event_handler, void *context);
+
+/*! \brief Pointer to function for getting encoder data.
+\param encoder pointer to a \a encoder_t struct.
+\returns pointer to a \a encoder_data_t struct containing the data.
+*/
+typedef encoder_data_t *(*encoder_get_data_ptr)(encoder_t *encoder);
+
+/*! \brief Pointer to the callbak function to be called by encoders_enumerate().
+\param encoder pointer to a \a encoder_t struct.
+\returns \a true to stop the enumeration and return true from encoders_enumerate(), \a false otherwise.
+*/
+typedef bool (*encoder_enumerate_callback_ptr)(encoder_t *encoder, void *data);
+
+/*! \brief Pointer to function for configuring an encoder.
+\param encoder pointer to a \a encoder_t struct.
+\param encoder pointer to a \a encoder_cfg_t struct.
+\returns \a true when claim was successful, \a false to otherwise.
+*/
+typedef bool (*encoder_configure_ptr)(encoder_t *encoder, encoder_cfg_t *settings);
+
+void encoder_register (encoder_t *encoder);
+bool encoders_enumerate (encoder_enumerate_callback_ptr callback, void *data);
+uint8_t encoders_get_count (void);
+
+struct encoder {
+ encoder_caps_t caps;
+ encoder_claim_ptr claim;
+ encoder_reset_ptr reset;
+ encoder_get_data_ptr get_data;
+ encoder_configure_ptr configure;
+};
+
+#endif // _ENCODERS_H_
+
+// Quadrature Encoder Interface - static code for drivers/plugins
+
+#if QEI_ENABLE && defined(QEI_A_PIN) && defined(QEI_B_PIN)
+
+typedef enum {
+ QEI_DirUnknown = 0,
+ QEI_DirCW,
+ QEI_DirCCW
+} qei_dir_t;
+
+typedef union {
+ uint_fast8_t pins;
+ struct {
+ uint_fast8_t a :1,
+ b :1;
+ };
+} qei_state_t;
+
+typedef struct {
+ encoder_t encoder;
+ encoder_data_t data;
+ encoder_event_t event;
+ void *context;
+ int32_t vel_count;
+ uint_fast16_t state;
+ qei_dir_t dir;
+ uint8_t port_a, port_b, port_select;
+ volatile uint32_t dbl_click_timeout;
+ volatile uint32_t vel_timeout;
+ uint32_t vel_timestamp;
+ encoder_on_event_ptr on_event;
+ encoder_cfg_t settings;
+} qei_t;
+
+static qei_t qei = {
+ .port_a = IOPORT_UNASSIGNED,
+ .port_b = IOPORT_UNASSIGNED,
+ .port_select = IOPORT_UNASSIGNED,
+ .settings.dbl_click_window = 500,
+ .encoder.caps.bidirectional = On
+};
+
+static void qei_post_event (void *data)
+{
+ qei.on_event(&qei.encoder, &qei.event, qei.context);
+}
+
+static void qei_dblclk_event (void *data)
+{
+ qei.event.dbl_click = On;
+ qei.on_event(&qei.encoder, &qei.event, qei.context);
+}
+
+static void qei_reset (encoder_t *encoder)
+{
+ qei.vel_timeout = 0;
+ qei.dir = QEI_DirUnknown;
+ qei.data.position = qei.vel_count = 0;
+ qei.vel_timestamp = hal.get_elapsed_ticks();
+ qei.vel_timeout = qei.settings.vel_timeout;
+}
+
+static bool qei_configure (encoder_t *encoder, encoder_cfg_t *settings)
+{
+ if(qei.vel_timeout != settings->vel_timeout)
+ qei.vel_timestamp = hal.get_elapsed_ticks();
+
+ memcpy(&qei.settings, settings, sizeof(encoder_cfg_t));
+
+ return true;
+}
+
+static encoder_data_t *qei_get_data (encoder_t *encoder)
+{
+ return &qei.data;
+}
+
+static void qei_poll (void *data)
+{
+ if(qei.vel_timeout && !(--qei.vel_timeout)) {
+
+ uint32_t time = hal.get_elapsed_ticks();
+
+ qei.data.velocity = abs(qei.data.position - qei.vel_count) * 1000 / (time - qei.vel_timestamp);
+ qei.vel_timestamp = time;
+ qei.vel_timeout = qei.settings.vel_timeout;
+ if((qei.event.position_changed = !qei.dbl_click_timeout || qei.data.velocity == 0))
+ qei.on_event(&qei.encoder, &qei.event, qei.context);
+ qei.vel_count = qei.data.position;
+ }
+
+ if(qei.dbl_click_timeout && !(--qei.dbl_click_timeout)) {
+ qei.event.click = On;
+ task_delete(qei_dblclk_event, NULL);
+ qei.on_event(&qei.encoder, &qei.event, qei.context);
+ }
+}
+
+static void qei_ab_irq (uint8_t port, bool high)
+{
+ const uint8_t encoder_valid_state[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};
+
+ static qei_state_t state = {0};
+
+ if(port == qei.port_a)
+ state.a = high;
+ else
+ state.b = high;
+
+ uint_fast8_t idx = (((qei.state << 2) & 0x0F) | state.pins);
+
+ if(encoder_valid_state[idx] ) {
+
+// int32_t count = qei.count;
+
+ qei.state = ((qei.state << 4) | idx) & 0xFF;
+
+ if(qei.state == 0x42 || qei.state == 0xD4 || qei.state == 0x2B || qei.state == 0xBD) {
+ qei.data.position--;
+ if(qei.vel_timeout == 0 || qei.dir == QEI_DirCW) {
+ qei.dir = QEI_DirCCW;
+ qei.event.position_changed = On;
+ task_add_immediate(qei_post_event, NULL);
+ }
+ } else if(qei.state == 0x81 || qei.state == 0x17 || qei.state == 0xE8 || qei.state == 0x7E) {
+ qei.data.position++;
+ if(qei.vel_timeout == 0 || qei.dir == QEI_DirCCW) {
+ qei.dir = QEI_DirCW;
+ qei.event.position_changed = On;
+ task_add_immediate(qei_post_event, NULL);
+ }
+ }
+ }
+}
+
+static void qei_select_irq (uint8_t port, bool high)
+{
+ if(high)
+ return;
+
+ if(!qei.dbl_click_timeout) {
+ qei.dbl_click_timeout = qei.settings.dbl_click_window;
+ } else if(qei.dbl_click_timeout < qei.settings.dbl_click_window) {
+ qei.dbl_click_timeout = 0;
+ task_delete(qei_dblclk_event, NULL);
+ task_add_immediate(qei_dblclk_event, NULL);
+ }
+}
+
+static bool qei_claim (encoder_on_event_ptr event_handler, void *context)
+{
+ if(event_handler == NULL || qei.on_event)
+ return false;
+
+ qei.context = context;
+ qei.on_event = event_handler;
+ qei.encoder.reset = qei_reset;
+ qei.encoder.get_data = qei_get_data;
+ qei.encoder.configure = qei_configure;
+
+ if(qei.port_b != IOPORT_UNASSIGNED) {
+ ioport_enable_irq(qei.port_a, IRQ_Mode_Change, qei_ab_irq);
+ ioport_enable_irq(qei.port_b, IRQ_Mode_Change, qei_ab_irq);
+ }
+
+ if(qei.port_select != IOPORT_UNASSIGNED)
+ ioport_enable_irq(qei.port_select, IRQ_Mode_Change, qei_select_irq);
+
+ task_add_systick(qei_poll, NULL);
+
+ return true;
+}
+
+static inline void encoder_pin_claimed (uint8_t port, xbar_t *pin)
+{
+ switch(pin->function) {
+
+ case Input_QEI_A:
+ qei.port_a = port;
+ break;
+
+ case Input_QEI_B:
+ qei.port_b = port;
+ qei.encoder.claim = qei_claim;
+ if(qei.port_a != IOPORT_UNASSIGNED)
+ encoder_register(&qei.encoder);
+ break;
+
+ case Input_QEI_Select:
+ qei.port_select = port;
+ qei.encoder.caps.select = On;
+ break;
+
+ default: break;
+ }
+}
+
+#endif
diff --git a/errors.c b/errors.c
index db2f9a5..6606930 100644
--- a/errors.c
+++ b/errors.c
@@ -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
@@ -107,7 +107,9 @@ PROGMEM static const status_detail_t status_detail[] = {
#endif
{ Status_FileOpenFailed, "Could not open file." },
{ Status_UserException, "User defined error occured." },
- { Status_AuxiliaryPortUnusable, "Port is not usable." }
+ { Status_AuxiliaryPortUnusable, "Port is not usable." },
+ { Status_ToolInSPindle, "Tool in spindle." },
+ { Status_NoToolInSPindle, "No tool in spindle." }
};
static error_details_t details = {
diff --git a/errors.h b/errors.h
index 5dfe777..bbbe9fa 100644
--- a/errors.h
+++ b/errors.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
@@ -119,7 +119,9 @@ typedef enum {
Status_FileOpenFailed = 84,
Status_FsFormatFailed = 85,
Status_AuxiliaryPortUnusable = 86,
- Status_StatusMax = Status_AuxiliaryPortUnusable,
+ Status_ToolInSPindle = 87,
+ Status_NoToolInSPindle = 88,
+ Status_StatusMax = Status_NoToolInSPindle,
Status_UserException = 253,
Status_Handled, // For internal use only
Status_Unhandled // For internal use only
diff --git a/gcode.c b/gcode.c
index 3f15487..374ed99 100644
--- a/gcode.c
+++ b/gcode.c
@@ -37,7 +37,6 @@
#if NGC_EXPRESSIONS_ENABLE
#include "ngc_expr.h"
#include "ngc_flowctrl.h"
-//#include "string_registers.h"
#ifndef NGC_N_ASSIGN_PARAMETERS_PER_BLOCK
#define NGC_N_ASSIGN_PARAMETERS_PER_BLOCK 10
#endif
@@ -63,8 +62,8 @@
typedef union {
uint32_t mask;
struct {
- uint32_t G0 :1, //!< [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1] Non-modal
- G1 :1, //!< [G0,G1,G2,G3,G33,G33.1,G38.2,G38.3,G38.4,G38.5,G76,G80] Motion
+ uint32_t G0 :1, //!< [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1,G92.2,G92.3] Non-modal
+ G1 :1, //!< [G0,G1,G2,G3,G33,G33.1,G38.2,G38.3,G38.4,G38.5,G76,G80,G81,G82,G83,G84,G85,G86,G89] Motion
G2 :1, //!< [G17,G18,G19] Plane selection
G3 :1, //!< [G90,G91] Distance mode
G4 :1, //!< [G91.1] Arc IJK distance mode
@@ -78,7 +77,7 @@ typedef union {
G13 :1, //!< [G61] Control mode (15)
G14 :1, //!< [G96,G97] Spindle Speed Mode (13)
G15 :1, //!< [G7,G8] Lathe Diameter Mode
-// G16 :1, //!< [G66,G67] Modal macro call (12)
+ G16 :1, //!< [G65,G66,G67,M98] Macro call (12)
M4 :1, //!< [M0,M1,M2,M30,M99] Stopping
M5 :1, //!< [M62,M63,M64,M65,M66,M67,M68] Aux I/O
@@ -130,6 +129,7 @@ DCRAM parser_state_t gc_state;
#define RETURN(status) return gc_at_exit(status);
m98_macro_t *m98_macros = NULL;
+static tool_data_t *pending_tool = NULL;
static output_command_t *output_commands = NULL; // Linked list
static scale_factor_t scale_factor = {
.ijk[X_AXIS] = 1.0f,
@@ -356,7 +356,7 @@ plane_t *gc_get_plane_data (plane_t *plane, plane_select_t select)
FLASHMEM axes_signals_t gc_claim_axis_words (parser_block_t *gc_block, axes_signals_t validate)
{
- static const parameter_words_t wordmap[] = {
+ PROGMEM static const parameter_words_t wordmap[] = {
{ .x = On },
{ .y = On },
{ .z = On },
@@ -401,7 +401,7 @@ FLASHMEM axes_signals_t gc_claim_axis_words (parser_block_t *gc_block, axes_sign
//Acceleration Profiles for G187 P[x] in percent of maximum machine acceleration.
float gc_get_accel_factor (uint8_t profile)
{
- static const float lookup[] = {
+ PROGMEM static const float lookup[] = {
1.0f, // 100% - Roughing - Max Acceleration Default
0.8f, // 80% - Semi Roughing
0.6f, // 60% - Semi Finish
@@ -416,7 +416,7 @@ float gc_get_accel_factor (uint8_t profile)
#if NGC_PARAMETERS_ENABLE
-FLASHMEM static parameter_words_t macro_arguments_push (gc_values_t *values, parameter_words_t words)
+FLASHMEM static parameter_words_t macro_arguments_push (gc_values_t *values, parameter_words_t words, gc_values_t *target)
{
// NOTE: this array has to match the parameter_words_t order!
PROGMEM static const uint8_t offset[] = {
@@ -481,7 +481,10 @@ FLASHMEM static parameter_words_t macro_arguments_push (gc_values_t *values, par
while(words.mask) {
if((words.mask & 1) && offset[idx]) {
g65_words.value |= (1 << idx);
- ngc_param_set((ngc_param_id_t)idx, *(float *)((uint8_t *)values + offset[idx]));
+ if(target)
+ *(float *)((uint8_t *)target + offset[idx]) = *(float *)((uint8_t *)values + offset[idx]);
+ else
+ ngc_param_set((ngc_param_id_t)idx, *(float *)((uint8_t *)values + offset[idx]));
}
idx++;
words.mask >>= 1;
@@ -695,6 +698,8 @@ static status_code_t gc_at_exit (status_code_t status)
{
if(!(status == Status_OK || status == Status_Handled)) {
+ pending_tool = NULL;
+
// Clear any pending output commands
gc_clear_output_commands(output_commands);
@@ -882,6 +887,19 @@ static inline void tool_set (tool_data_t *tool)
gc_state.tool = tool;
else
gc_state.tool->tool_id = tool->tool_id;
+
+ pending_tool = NULL;
+}
+
+// For core use only
+FLASHMEM void gc_tool_changed (void)
+{
+ if(pending_tool)
+ tool_set(pending_tool);
+
+ grbl.on_tool_changed(gc_state.tool);
+
+ report_add_realtime(Report_Tool);
}
// Add output command to linked list
@@ -1051,7 +1069,7 @@ char *gc_normalize_block (char *block, status_code_t *status, char **message)
status_code_t gc_execute_block (char *block)
{
- static const parameter_words_t axis_words_mask = {
+ PROGMEM static const parameter_words_t axis_words_mask = {
.x = On,
.y = On,
.z = On,
@@ -1075,33 +1093,43 @@ status_code_t gc_execute_block (char *block)
#endif
};
- static const parameter_words_t pq_words = {
+ PROGMEM static const parameter_words_t pq_words = {
.p = On,
.q = On
};
- static const parameter_words_t ij_words = {
+ PROGMEM static const parameter_words_t ij_words = {
.i = On,
.j = On
};
- static const parameter_words_t uint_words = {
+ PROGMEM static const parameter_words_t uint_words = {
.l = On,
.n = On,
.o = On,
};
- static const modal_groups_t jog_groups = {
+ PROGMEM static const modal_groups_t jog_groups = {
.G0 = On,
.G3 = On,
.G6 = On
};
+ PROGMEM static const modal_groups_t m_groups = {
+ .M4 = On,
+ .M5 = On,
+ .M6 = On,
+ .M7 = On,
+ .M8 = On,
+ .M9 = On,
+ .M10 = On
+ };
+
DCRAM static parser_block_t gc_block;
#if NGC_EXPRESSIONS_ENABLE
- static const parameter_words_t o_label = {
+ PROGMEM static const parameter_words_t o_label = {
.o = On
};
@@ -1113,7 +1141,7 @@ status_code_t gc_execute_block (char *block)
#ifdef ROTATION_ENABLE
- static const axes_signals_t rotate_axes[] = {
+ PROGMEM static const axes_signals_t rotate_axes[] = {
{ .x = On, .y = On },
{ .x = On, .z = On },
{ .y = On, .z = On }
@@ -1180,7 +1208,7 @@ status_code_t gc_execute_block (char *block)
memcpy(&gc_block.modal, &gc_state.modal, offsetof(gc_modal_t, tool_length_offset)); // Copy current modes
int32_t spindle_id = 0;
- bool set_tool = false, spindle_event = false;
+ bool spindle_event = false;
axis_command_t axis_command = AxisCommand_None;
io_mcode_t port_command = (io_mcode_t)0;
spindle_t *sspindle = gc_state.spindle;
@@ -1268,6 +1296,7 @@ status_code_t gc_execute_block (char *block)
RETURN(status);
continue;
+
} else if(letter == 'O') {
gc_block.words.n = Off; // Hack to allow line number with O word
@@ -1342,6 +1371,13 @@ status_code_t gc_execute_block (char *block)
}
// NOTE: Rounding must be used to catch small floating point errors.
+#if NGC_PARAMETERS_ENABLE
+ if(gc_state.g66_args && gc_state.g66_args->call == MacroCall_Modal1 && gc_state.g66_args->call_level == ngc_call_level()) {
+ command_words.G16 = On;
+ gc_block.macro_call = gc_state.g66_args->call;
+ }
+#endif
+
// Check if the g-code word is supported or errors due to modal group violations or has
// been repeated in the g-code block. If ok, update the command or record its value.
switch(letter) {
@@ -1538,11 +1574,26 @@ status_code_t gc_execute_block (char *block)
case 65:
case 66:
+ // NOTE: Fanuc style, not supported by LinuxCNC
+ if(command_words.mask & m_groups.mask) {
+ gc_block.words.m = On;
+ command_words.mask &= ~m_groups.mask;
+ }
+ if(grbl.on_macro_execute && mantissa <= (int_value == 66 ? 10 : 0)) {
+ word_bit.modal_group.G16 = On;
+ gc_block.macro_call = (macro_call_t)(int_value + mantissa * 10);
+ mantissa = 0;
+ } else
+ RETURN(Status_GcodeUnsupportedCommand);
+ break;
+
case 67:
- // NOTE: Not supported by LinuxCNC
- word_bit.modal_group.G0 = On;
- gc_block.non_modal_command = (non_modal_t)int_value;
- if(mantissa != 0 || grbl.on_macro_execute == NULL)
+ // NOTE: Fanuc style, not supported by LinuxCNC
+ if(grbl.on_macro_execute) {
+ if(!(command_words.G16 && gc_state.g66_args))
+ word_bit.modal_group.G16 = On;
+ gc_block.macro_call = MacroCall_End;
+ } else
RETURN(Status_GcodeUnsupportedCommand);
break;
@@ -1590,13 +1641,13 @@ status_code_t gc_execute_block (char *block)
case 'M': // Determine 'M' command and its modal group
- if(gc_block.non_modal_command == NonModal_MacroCall ||
- gc_block.non_modal_command == Modal_MacroCall) {
+ gc_block.values.m = value;
+
+ if(gc_block.macro_call == MacroCall_NonModal || gc_block.macro_call == MacroCall_Modal || gc_block.macro_call == MacroCall_Modal1) {
if(gc_block.words.m)
RETURN(Status_GcodeWordRepeated); // [Word repeated]
- gc_block.values.m = value;
gc_block.words.m = On; // Flag to indicate parameter assigned.
continue;
@@ -1639,9 +1690,10 @@ status_code_t gc_execute_block (char *block)
{
atc_status_t atc = hal.tool.atc_get_state();
if(atc != ATC_None || settings.tool_change.mode != ToolChange_Ignore) {
- if(atc == ATC_None ? !!hal.stream.suspend_read : (atc == ATC_Online && hal.tool.change))
+ if(atc == ATC_None ? !!hal.stream.suspend_read : (atc == ATC_Online && hal.tool.change)) {
word_bit.modal_group.M6 = On;
- else
+ gc_block.tool_action = ToolAction_Change;
+ } else
RETURN(Status_GcodeUnsupportedCommand); // [Unsupported M command]
}
}
@@ -1680,8 +1732,8 @@ status_code_t gc_execute_block (char *block)
break;
case 61:
- set_tool = true;
word_bit.modal_group.M6 = On; //??
+ gc_block.tool_action = ToolAction_Set;
break;
case 62:
@@ -1720,8 +1772,8 @@ status_code_t gc_execute_block (char *block)
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;
+ word_bit.modal_group.G16 = On;
+ gc_block.macro_call = MacroCall_NonModal98;
break;
case 99:
@@ -1804,7 +1856,8 @@ status_code_t gc_execute_block (char *block)
break;
case 'H':
- gc_block.values.h = isnan(value) ? 0xFFFFFFFF : int_value;
+ word_bit.parameter.h = On;
+ gc_block.values.h = value;
break;
case 'I':
@@ -1838,9 +1891,7 @@ status_code_t gc_execute_block (char *block)
break;
case 'O':
- if(mantissa > 0)
- RETURN(Status_GcodeCommandValueNotInteger);
- {
+ if(mantissa == 0) {
m98_macro_t *macro;
if(hal.stream.state.m98_macro_prescan && (macro = macro_find((macro_id_t)int_value))) {
@@ -1851,10 +1902,11 @@ status_code_t gc_execute_block (char *block)
word_bit.parameter.o = On;
gc_block.values.o = int_value;
}
- }
+ } else
+ RETURN(Status_GcodeCommandValueNotInteger);
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.
word_bit.parameter.p = On;
gc_block.values.p = value;
break;
@@ -2033,51 +2085,63 @@ status_code_t gc_execute_block (char *block)
// [0. Non-specific/common error-checks and miscellaneous setup]:
- if((word_bit.modal_group.G0 &&
- (gc_block.non_modal_command == Modal_MacroCall ||
- gc_block.non_modal_command == NonModal_MacroCall)) ||
- (word_bit.modal_group.M4 &&
- gc_block.non_modal_command == NonModal_MacroCall2)) {
+ if(command_words.G16) {
- if(!gc_block.words.p)
- RETURN(Status_GcodeValueWordMissing); // [P word missing]
- if(gc_block.values.p > 65535.0f)
- RETURN(Status_GcodeValueOutOfRange); // [P word out of range]
+ if(gc_block.macro_call && gc_state.g66_args == NULL) {
- if(!gc_block.words.l || gc_block.values.l == 0)
- gc_block.values.l = 1;
+ if(!gc_block.words.p)
+ RETURN(Status_GcodeValueWordMissing); // [P word missing]
+ if(gc_block.values.p > 65535.0f)
+ RETURN(Status_GcodeValueOutOfRange); // [P word out of range]
+
+ if(!gc_block.words.l || gc_block.values.l == 0)
+ gc_block.values.l = 1;
+ }
gc_block.words.l = gc_block.words.p = Off;
#if NGC_PARAMETERS_ENABLE
- if(gc_block.non_modal_command != NonModal_MacroCall2) {
+ if(gc_block.macro_call == MacroCall_NonModal || gc_block.macro_call == MacroCall_Modal || gc_block.macro_call == MacroCall_Modal1) {
// Remove axis and ijk words flags since values are to be passed unmodified.
axis_words.mask = ijk_words.mask = 0;
- if(gc_block.non_modal_command == NonModal_MacroCall) {
+ // Remove all modal groups except macro call.
+ command_words.mask = (modal_groups_t){ .G16 = On }.mask;
+
+ if(gc_block.macro_call == MacroCall_NonModal) {
// TODO: add context for local storage?
if(!ngc_call_push(&gc_state + ngc_call_level()))
RETURN(Status_FlowControlStackOverflow); // [Call level too deep]
- gc_block.g65_words.mask = macro_arguments_push(&gc_block.values, gc_block.words).mask;
+ gc_block.g65_words.mask = macro_arguments_push(&gc_block.values, gc_block.words, NULL).mask;
gc_block.words.mask &= ~gc_block.g65_words.mask; // Remove G65 arguments
- } else { // G66
+ } else { // G66 and G66.1
g66_arguments_t *args;
// if(gc_state.g66_args && gc_state.g66_args->call_level == ngc_call_level())
// RETURN(cannot have nested g66 calls on the same call level?);
- if((args = malloc(sizeof(g66_arguments_t)))) {
+ if(gc_state.g66_args && gc_state.g66_args->call == MacroCall_Modal1 && gc_state.g66_args->call_level == ngc_call_level()) {
+ args = gc_state.g66_args;
+ gc_block.words.mask &= ~macro_arguments_push(&gc_block.values, (parameter_words_t){ .mask = gc_block.words.mask & gc_state.g66_args->words.mask }, &gc_state.g66_args->values).mask;
+ } else if((args = malloc(sizeof(g66_arguments_t)))) {
args->prev = gc_state.g66_args;
gc_state.g66_args = args;
+ gc_state.g66_args->call = gc_block.macro_call;
gc_state.g66_args->call_level = ngc_call_level();
gc_state.g66_args->words.mask = gc_block.words.mask;
+ gc_block.words.mask = 0;
memcpy(&gc_state.g66_args->values, &gc_block.values, sizeof(gc_values_t));
}
- RETURN(args ? Status_OK : Status_FlowControlStackOverflow);
+ if(args == NULL)
+ RETURN(Status_FlowControlStackOverflow);
+ // MacroCall_Modal is not invoked on initially, MacroCall_Modal1 is.
+ if(gc_block.macro_call == MacroCall_Modal)
+ return Status_OK;
}
}
#endif // NGC_PARAMETERS_ENABLE
- }
+ } else
+ gc_block.words.m = Off;
// Determine implicit axis command conditions. Axis words have been passed, but no explicit axis
// command has been sent. If so, set axis command to current motion mode.
@@ -2291,7 +2355,8 @@ status_code_t gc_execute_block (char *block)
// bit_false(gc_block.words,bit(Word_S)); // NOTE: Single-meaning value word. Set at end of error-checking.
// [5. Select tool ]: If not supported then only tracks value. T is negative (done.) Not an integer (done).
- if(set_tool) { // M61
+ if(gc_block.tool_action == ToolAction_Set) { // M61
+
if(!gc_block.words.q)
RETURN(Status_GcodeValueWordMissing);
if(!isintf(gc_block.values.q))
@@ -2300,16 +2365,18 @@ status_code_t gc_execute_block (char *block)
RETURN(Status_GcodeIllegalToolTableEntry);
gc_block.values.t = gc_block.values.q;
- gc_block.words.q = Off;
+ command_words.M6 = gc_block.words.q = Off;
#if NGC_EXPRESSIONS_ENABLE
if(hal.stream.file) {
- gc_state.tool_pending = (uint32_t)-1; // force set tool
+ gc_state.tool_pending = (tool_id_t)-1; // force set tool
if(grbl.tool_table.n_tools) {
- if(gc_state.g43_pending) {
- gc_block.values.h = (float)gc_state.g43_pending;
+ if((gc_block.words.h = gc_state.g43_pending > 0)) { // ?? --> >= 0
command_words.G8 = On;
+ gc_block.words.h = On;
+ gc_block.values.h = (float)gc_state.g43_pending;
+ gc_block.modal.tool_offset_mode = ToolLengthOffset_Enable;
}
- gc_state.g43_pending = 0;
+ gc_state.g43_pending = (tool_id_t)-1;
}
}
#endif
@@ -2388,7 +2455,9 @@ status_code_t gc_execute_block (char *block)
// bit_false(gc_block.words,bit(Word_T)); // NOTE: Single-meaning value word. Set at end of error-checking.
- // [6. Change tool ]: N/A
+ // [6. Change tool ]:
+ if(command_words.M6 && gc_block.tool_action == ToolAction_Change && (tool_id_t)gc_block.values.t == gc_state.tool->tool_id)
+ command_words.M6 = Off; // Tool already in spindle, ignore.
// [7. Spindle control ]:
if(command_words.M7) {
@@ -2642,8 +2711,8 @@ status_code_t gc_execute_block (char *block)
if(!grbl.tool_table.get_tool((tool_id_t)gc_block.values.h)->data)
RETURN(Status_GcodeIllegalToolTableEntry);
gc_block.words.h = Off;
- } else
- gc_block.values.h = gc_block.values.t;
+ } else if(!command_words.M6)
+ gc_block.values.h = (float)gc_state.tool->tool_id;
} else
RETURN(Status_GcodeUnsupportedCommand);
break;
@@ -2701,27 +2770,45 @@ status_code_t gc_execute_block (char *block)
// all the current coordinate system and G92 offsets.
switch (gc_block.non_modal_command) {
- case NonModal_SetCoordinateData:
+ case NonModal_Settings:;
// [G10 Errors]: L missing and is not 2 or 20. P word missing. (Negative P value done.)
// [G10 L2 Errors]: R word NOT SUPPORTED. P value not 0 to N_WorkCoordinateSystems (max 9). Axis words missing.
// [G10 L20 Errors]: P must be 0 to N_WorkCoordinateSystems (max 9). Axis words missing.
+ // [G10 L0 Errors]: No tool table or tool in spindle.
// [G10 L1, L10, L11 Errors]: P must be 0 to grbl.tool_table.n_tools. Axis words or R word missing.
- if (!(axis_words.mask || (gc_block.values.l != 20 && gc_block.words.r)))
- RETURN(Status_GcodeNoAxisWords); // [No axis words (or R word for tool offsets)]
+ uint8_t p_value = 0;
- if (!(gc_block.words.p || gc_block.words.l))
- RETURN(Status_GcodeValueWordMissing); // [P/L word missing]
+ if(gc_block.values.l > 0) {
- if(gc_block.values.p < 0.0f)
- RETURN(Status_NegativeValue);
+ if(!(axis_words.mask || (gc_block.values.l != 20 && gc_block.words.r)))
+ RETURN(Status_GcodeNoAxisWords); // [No axis words (or R word for tool offsets)]
- uint8_t p_value;
+ if(!(gc_block.words.p || gc_block.words.l))
+ RETURN(Status_GcodeValueWordMissing); // [P/L word missing]
- p_value = (uint8_t)truncf(gc_block.values.p); // Convert p value to int.
+ if(gc_block.values.p < 0.0f)
+ RETURN(Status_NegativeValue);
+
+ p_value = (uint8_t)truncf(gc_block.values.p); // Convert p value to int.
+ }
switch(gc_block.values.l) {
+
+ case 0:
+ if(grbl.tool_table.reload == NULL)
+ RETURN(Status_GcodeUnsupportedCommand); // [G10 L0 not supported]
+ if(gc_state.tool->tool_id != 0)
+ RETURN(Status_ToolInSPindle); // [G1 L0 can only be used when no tool is loaded]
+ if((status_code_t)(int_value = grbl.tool_table.reload()) == Status_OK) {
+ tool_data_t *tool_data;
+ if((tool_data = grbl.tool_table.get_tool((tool_id_t)gc_state.tool->tool_id)->data))
+ memcpy(&gc_state.modal.tool_length_offset, tool_data->offset.values, sizeof(gc_state.modal.tool_length_offset));
+ } else
+ RETURN((status_code_t)int_value);
+ break;
+
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
@@ -3574,6 +3661,28 @@ status_code_t gc_execute_block (char *block)
RETURN((status_code_t)int_value);
}
+ bool check_mode = state_get() == STATE_CHECK_MODE;
+
+ if(command_words.G16) switch(gc_block.macro_call) {
+
+ case MacroCall_NonModal:
+ RETURN(macro_call((macro_id_t)gc_block.values.p, gc_block.g65_words, gc_block.values.l));
+ break;
+
+#if NGC_PARAMETERS_ENABLE
+ case MacroCall_End:
+ if(gc_state.g66_args) {
+ g66_arguments_t *args = gc_state.g66_args->prev;
+ free(gc_state.g66_args);
+ gc_state.g66_args = args;
+ }
+ break;
+#endif
+
+ default:
+ break;
+ }
+
// If in laser mode, setup laser power based on current and past parser conditions.
if(sspindle && sspindle->hal->cap.laser) {
@@ -3602,8 +3711,6 @@ status_code_t gc_execute_block (char *block)
// NOTE: If no line number is present, the value is zero.
plan_data.line_number = gc_state.line_number = (uint32_t)gc_block.values.n; // Record data for planner use.
- bool check_mode = state_get() == STATE_CHECK_MODE;
-
// [1. Comments feedback ]: Extracted in protocol.c if HAL entry point provided
if(message && !check_mode && (plan_data.message = malloc(strlen(message) + 1)))
strcpy(plan_data.message, message);
@@ -3662,18 +3769,22 @@ status_code_t gc_execute_block (char *block)
if(!check_mode) {
- tool_data_t *pending_tool = tool_get_pending(gc_state.tool_pending, NULL);
+ bool set_current;
+ tool_data_t *tool = tool_get_pending(gc_state.tool_pending, NULL);
// If M6 not available or M61 commanded set new tool immediately
- if(set_tool || (hal.driver_cap.atc ? !hal.tool.change : settings.tool_change.mode == ToolChange_Ignore || !(hal.stream.suspend_read || hal.tool.change))) {
+ if((set_current = gc_block.tool_action == ToolAction_Set || (hal.driver_cap.atc
+ ? !hal.tool.change
+ : settings.tool_change.mode == ToolChange_Ignore ||
+ !(hal.stream.suspend_read || hal.tool.change)))) {
- tool_set(pending_tool);
+ tool_set(tool);
if(grbl.on_tool_selected) {
spindle_state_t state = sspindle ? sspindle->state : (spindle_state_t){0};
- grbl.on_tool_selected(pending_tool);
+ grbl.on_tool_selected(tool);
if(sspindle && state.value != sspindle->state.value) {
command_words.M7 = On;
@@ -3681,15 +3792,12 @@ status_code_t gc_execute_block (char *block)
}
}
- if(grbl.on_tool_changed)
- grbl.on_tool_changed(gc_state.tool);
-
- report_add_realtime(Report_Tool);
+ gc_tool_changed();
}
// Prepare tool carousel when available
if(hal.tool.select)
- hal.tool.select(pending_tool, !set_tool);
+ hal.tool.select(tool, !set_current);
else
report_add_realtime(Report_Tool);
}
@@ -3727,9 +3835,9 @@ status_code_t gc_execute_block (char *block)
}
// [6. Change tool ]: Delegated to (possible) driver implementation
- if(command_words.M6 && !set_tool && !check_mode) {
+ if(command_words.M6 && gc_block.tool_action == ToolAction_Change && !check_mode) {
- tool_data_t *pending_tool = tool_get_pending(gc_state.tool_pending, plan_data.message ? NULL : &plan_data.message);
+ pending_tool = tool_get_pending(gc_state.tool_pending, plan_data.message ? NULL : &plan_data.message);
protocol_buffer_synchronize();
@@ -3769,18 +3877,14 @@ status_code_t gc_execute_block (char *block)
protocol_execute_realtime(); // Execute...
}
#if NGC_EXPRESSIONS_ENABLE
- if((status_code_t)int_value != Status_Unhandled)
- tool_set(pending_tool);
- else if(grbl.tool_table.n_tools && command_words.G8 && gc_block.modal.tool_offset_mode && ToolLengthOffset_Enable) {
- gc_state.g43_pending = gc_block.values.h;
- command_words.G8 = Off;
+ if((status_code_t)int_value == Status_Unhandled) &&
+ grbl.tool_table.n_tools && command_words.G8 && gc_block.modal.tool_offset_mode == ToolLengthOffset_Enable) {
+ gc_state.g43_pending = gc_block.words.h ? (tool_id_t)gc_block.values.h : pending_tool->tool_id;
+ command_words.G8 = gc_block.words.h = Off;
}
-#else
- tool_set(pending_tool);
#endif
-
- if(grbl.on_tool_changed && !macro_toolchange && state_get() != STATE_TOOL_CHANGE)
- grbl.on_tool_changed(gc_state.tool);
+ if(!macro_toolchange && state_get() != STATE_TOOL_CHANGE)
+ gc_tool_changed();
}
}
@@ -3944,10 +4048,12 @@ status_code_t gc_execute_block (char *block)
// [9. Override control ]:
if(command_words.M9) {
- gc_state.modal.override_ctrl = gc_block.modal.override_ctrl;
-
- if(gc_state.modal.override_ctrl.feed_rates_disable)
+ if(gc_state.modal.override_ctrl.feed_rates_disable != gc_block.modal.override_ctrl.feed_rates_disable) {
+ sys.override.control.feed_rates_disable = gc_block.modal.override_ctrl.feed_rates_disable;
plan_feed_override(0, 0);
+ }
+
+ gc_state.modal.override_ctrl = gc_block.modal.override_ctrl;
mc_override_ctrl_update(gc_state.modal.override_ctrl); // NOTE: must be called last!
}
@@ -4068,7 +4174,7 @@ status_code_t gc_execute_block (char *block)
// [19. Go to predefined position, set G10, or set axis offsets ]:
switch(gc_block.non_modal_command) {
- case NonModal_SetCoordinateData:
+ case NonModal_Settings:
if(gc_block.values.l == 2 || gc_block.values.l == 20) {
settings_write_coord_data(coord_system.id, &coord_system.data);
@@ -4182,28 +4288,6 @@ status_code_t gc_execute_block (char *block)
settings_write_coord_data(CoordinateSystem_G30, &coord_system.data);
break;
- case NonModal_MacroCall:
- RETURN(macro_call((macro_id_t)gc_block.values.p, gc_block.g65_words, gc_block.values.l));
- break;
-
-#if NGC_PARAMETERS_ENABLE
- case Modal_MacroEnd:
- if(gc_state.g66_args) {
- g66_arguments_t *args = gc_state.g66_args->prev;
- free(gc_state.g66_args);
- gc_state.g66_args = args;
- }
- break;
-#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
add_offset((coord_data_t *)gc_block.values.xyz);
gc_state.g92_offset_applied = true; // TODO: check for all zero?
@@ -4245,6 +4329,7 @@ status_code_t gc_execute_block (char *block)
if(gc_state.modal.motion != MotionMode_None && axis_command == AxisCommand_MotionMode) {
plan_data.output_commands = output_commands;
+ plan_data.condition.no_feed_override = gc_state.modal.override_ctrl.feed_rates_disable;
#if ENABLE_PATH_BLENDING
plan_data.cam_tolerance = gc_state.cam_tolerance;
plan_data.path_tolerance = gc_state.path_tolerance;
@@ -4252,10 +4337,6 @@ status_code_t gc_execute_block (char *block)
#if PLANNER_ADD_MOTION_MODE
plan_data.motion_mode = gc_state.modal.motion;
#endif
-#if NGC_PARAMETERS_ENABLE
- bool call_g66_macro = false;
-#endif
-
pos_update_t gc_update_pos = GCUpdatePos_Target;
switch(gc_state.modal.motion) {
@@ -4269,7 +4350,8 @@ status_code_t gc_execute_block (char *block)
}
mc_line(gc_block.values.xyz, &plan_data);
#if NGC_PARAMETERS_ENABLE
- call_g66_macro = gc_state.g66_args && gc_state.g66_args->call_level == ngc_call_level();
+ if((command_words.G16 = gc_state.g66_args && gc_state.g66_args->call_level == ngc_call_level()))
+ gc_block.macro_call = gc_state.g66_args->call;
#endif
break;
@@ -4277,7 +4359,9 @@ status_code_t gc_execute_block (char *block)
plan_data.condition.rapid_motion = On; // Set rapid motion condition flag.
mc_line(gc_block.values.xyz, &plan_data);
#if NGC_PARAMETERS_ENABLE
- call_g66_macro = gc_state.g66_args && gc_state.g66_args->call_level == ngc_call_level();
+ // Run G66 macro?
+ if((command_words.G16 = gc_state.g66_args && gc_state.g66_args->call_level == ngc_call_level()))
+ gc_block.macro_call = gc_state.g66_args->call;
#endif
break;
@@ -4329,7 +4413,7 @@ status_code_t gc_execute_block (char *block)
RETURN(status);
plan_data.spindle.state.synchronized = On;
- plan_data.overrides.feed_hold_disable = On; // Disable feed hold.
+ plan_data.overrides.feed_hold_disable = plan_data.condition.no_feed_override = On; // Disable feed hold and override.
mc_line(gc_block.values.xyz, &plan_data);
@@ -4400,18 +4484,32 @@ status_code_t gc_execute_block (char *block)
else if (gc_update_pos == GCUpdatePos_System)
gc_sync_position(); // gc_state.position[] = sys.position
// == GCUpdatePos_None
+ }
+
+ if(command_words.G16) switch(gc_block.macro_call) {
#if NGC_PARAMETERS_ENABLE
- if(call_g66_macro) {
-
+ case MacroCall_Modal:
+ case MacroCall_Modal1:
if(!ngc_call_push(&gc_state + ngc_call_level()))
RETURN(Status_FlowControlStackOverflow); // [Call level too deep]
- macro_arguments_push(&gc_state.g66_args->values, gc_state.g66_args->words);
+ macro_arguments_push(&gc_state.g66_args->values, gc_state.g66_args->words, NULL);
RETURN(macro_call((macro_id_t)gc_state.g66_args->values.p, gc_state.g66_args->words, gc_state.g66_args->values.l));
- }
+ break;
#endif
+
+ case MacroCall_NonModal98:
+ 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;
+
+ default:
+ break;
}
if(plan_data.message)
@@ -4485,10 +4583,8 @@ status_code_t gc_execute_block (char *block)
settings.parking.flags.deactivate_upon_init;
sys.override.control = gc_state.modal.override_ctrl;
- if(settings.flags.restore_overrides) {
- sys.override.feed_rate = DEFAULT_FEED_OVERRIDE;
- sys.override.rapid_rate = DEFAULT_RAPID_OVERRIDE;
- }
+ if(settings.flags.restore_overrides)
+ plan_feed_override(DEFAULT_FEED_OVERRIDE, DEFAULT_RAPID_OVERRIDE);
// Execute coordinate change and spindle/coolant stop.
if(!check_mode) {
diff --git a/gcode.h b/gcode.h
index 7d2a8f5..962c16d 100644
--- a/gcode.h
+++ b/gcode.h
@@ -51,27 +51,36 @@ Do not alter values!
typedef enum {
NonModal_NoAction = 0, //!< 0 - Default, must be zero
NonModal_Dwell = 4, //!< 4 - G4
- NonModal_SetCoordinateData = 10, //!< 10 - G10
+ NonModal_Settings = 10, //!< 10 - G10
NonModal_GoHome_0 = 28, //!< 28 - G28
NonModal_SetHome_0 = 38, //!< 38 - G28.1
NonModal_GoHome_1 = 30, //!< 30 - G30
NonModal_SetHome_1 = 40, //!< 40 - G30.1
NonModal_AbsoluteOverride = 53, //!< 53 - G53
- NonModal_MacroCall = 65, //!< 65 - G65
- Modal_MacroCall = 66, //!< 66 - G66
- Modal_MacroEnd = 67, //!< 67 - G67
NonModal_SetCoordinateOffset = 92, //!< 92 - G92
- NonModal_MacroCall2 = 98, //!< 98 - M98
NonModal_ResetCoordinateOffset = 102, //!< 102 - G92.1
NonModal_ClearCoordinateOffset = 112, //!< 112 - G92.2
#if ENABLE_ACCELERATION_PROFILES
NonModal_RestoreCoordinateOffset = 122, //!< 122 - G92.3
NonModal_SetAccelerationProfile = 187 //!< 187 - G187
#else
- NonModal_RestoreCoordinateOffset = 122 //!< 122 - G92.3
+ NonModal_RestoreCoordinateOffset = 122 //!< 122 - G92.3
#endif
} non_modal_t;
+typedef enum {
+ ToolAction_None = 0, //!< 0 - Default, must be zero
+ ToolAction_Change = 6, //!< 6 - M6
+ ToolAction_Set = 61, //!< 61 - M61
+} tool_action_t;
+
+typedef enum {
+ MacroCall_End = 0, //!< 0 - Default, must be zero (G67)
+ MacroCall_NonModal = 65, //!< 65 - G65
+ MacroCall_Modal = 66, //!< 66 - G66
+ MacroCall_Modal1 = 166, //!< 166 - G66.1
+ MacroCall_NonModal98 = 98, //!< 98 - M98
+} macro_call_t;
typedef enum {
ModalState_NoAction = 0, //!< 0 - Default, must be zero
@@ -602,6 +611,7 @@ typedef struct {
typedef struct g66_arguments
{
+ macro_call_t call;
uint32_t call_level;
gc_values_t values;
parameter_words_t words;
@@ -624,10 +634,10 @@ typedef struct {
float path_tolerance; //!< Path blending tolerance
float cam_tolerance; //!< Naive CAM tolerance
#endif
- uint32_t line_number; //!< Last line number sent
+ uint32_t line_number; //!< Last line number sent
tool_id_t tool_pending; //!< Tool to be selected on next M6
#if NGC_EXPRESSIONS_ENABLE
- uint32_t g43_pending; //!< Tool offset to be selected on next M6, for macro ATC
+ tool_id_t g43_pending; //!< Tool offset to be selected on next M6, for macro ATC
#endif
bool file_run; //!< Tracks % command
bool file_stream; //!< Tracks streaming from file
@@ -663,6 +673,7 @@ It will also be passed to mc_jog_execute() and any user M-code validation and ex
*/
typedef struct {
non_modal_t non_modal_command; //!< Non modal command
+ tool_action_t tool_action; //!< Non modal tool change
override_mode_t override_command; //!< Override command TODO: add to non_modal above?
user_mcode_t user_mcode; //!< Set > 0 if a user M-code is found.
bool user_mcode_sync; //!< Set to \a true by M-code validation handler if M-code is to be executed after synchronization.
@@ -674,6 +685,7 @@ typedef struct {
uint32_t arc_turns; //
parameter_words_t g65_words; //!< Parameter words to pass to G65 macro.
#if NGC_PARAMETERS_ENABLE
+ macro_call_t macro_call;
modal_state_action_t state_action; //!< M70-M73 modal state action
#endif
#if N_AXIS > 3
diff --git a/grbl.h b/grbl.h
index 6676dfd..7ac299e 100644
--- a/grbl.h
+++ b/grbl.h
@@ -42,7 +42,7 @@
#else
#define GRBL_VERSION "1.1f"
#endif
-#define GRBL_BUILD 20260218
+#define GRBL_BUILD 20260225
#define GRBL_URL "https://github.com/grblHAL"
diff --git a/hal.h b/hal.h
index 26bbe71..f1033e9 100644
--- a/hal.h
+++ b/hal.h
@@ -453,32 +453,6 @@ typedef struct {
atc_get_state_ptr atc_get_state; //!< Optional handler for checking ATC status.
} tool_ptrs_t;
-/*******************
- * Encoder input *
- *******************/
-
-/*! \brief Pointer to function for getting number of encoders supported.
-\returns number of encoders.
-*/
-typedef uint8_t (*encoder_get_n_encoders_ptr)(void);
-
-/*! \brief Pointer to callback function to receive encoder events.
-\param encoder pointer to a \a encoder_t struct.
-\param position encoder position.
-*/
-typedef void (*encoder_on_event_ptr)(encoder_t *encoder, int32_t position);
-
-/*! \brief Pointer to function for resetting encoder data.
-\param id encoder id.
-*/
-typedef void (*encoder_reset_ptr)(uint_fast8_t id);
-
-typedef struct {
- encoder_get_n_encoders_ptr get_n_encoders; //!< Optional handler for getting number of encoders supported.
- encoder_on_event_ptr on_event; //!< Optional callback handler for receiving encoder events.
- encoder_reset_ptr reset; //!< Optional handler for resetting data for an encoder.
-} encoder_ptrs_t;
-
/*! \brief Pointer to callback function to receive spindle encoder index events.
\param count index pulse count.
*/
@@ -513,7 +487,7 @@ typedef union {
comp1 :1, //!< Timer supports compare interrupt 0
comp2 :1, //!< Timer supports compare interrupt 1
ext_clk :1, //!< External clock supported
- encoder :1, //!< Emcode mode supported
+ encoder :1, //!< Encode mode supported
unused :2;
};
} timer_cap_t;
@@ -691,7 +665,6 @@ typedef struct {
pallet_shuttle_ptr pallet_shuttle; //!< Optional handler for performing a pallet shuttle on program end (M60).
void (*reboot)(void); //!< Optoional handler for rebooting the controller. This will be called when #ASCII_ESC followed by #CMD_REBOOT is received.
- encoder_ptrs_t encoder; //!< Optional handlers for encoder support.
spindle_encoder_on_index_ptr spindle_encoder_on_index; //!< Optional handler (callback) to receive spindle encoder index event.
/*! \brief Optional handler for getting the current axis positions.
diff --git a/modbus.c b/modbus.c
index 49de418..b61ffd6 100644
--- a/modbus.c
+++ b/modbus.c
@@ -223,14 +223,14 @@ FLASHMEM static void rx_packet (modbus_message_t *msg)
break;
case ModBus_ReadCoils:
case ModBus_ReadDiscreteInputs:
- response.num_values = min(msg->adu[2], 3); // byte count, not /2
+ response.num_values = min(msg->adu[2], MODBUS_MAX_REGISTERS); // byte count
for(idx = 0; idx < response.num_values; idx++)
- response.values[idx] = msg->adu[3 + idx]; // single bytes, not u16
+ response.values[idx] = msg->adu[3 + idx];
break;
default:;
response.num_values = cmds[response.function].single_register || cmds[response.function].is_write ? 2 : msg->adu[2] / 2;
- response.num_values = min(response.num_values, 3);
+ response.num_values = min(response.num_values, MODBUS_MAX_REGISTERS);
uint_fast8_t pos = cmds[response.function].single_register || cmds[response.function].is_write ? 2 : 3;
for(idx = 0; idx < response.num_values; idx++)
response.values[idx] = modbus_read_u16(&msg->adu[pos + (idx << 1)]);
@@ -303,9 +303,9 @@ FLASHMEM status_code_t modbus_message (uint8_t server, modbus_function_t functio
cmd.adu[5] = (uint8_t)(registers & 0xFF);
if(function == ModBus_ReadCoils || function == ModBus_ReadDiscreteInputs)
- cmd.rx_length = 5 + ((registers + 7) / 8); // bit-packed, ceil(n/8) bytes
+ cmd.rx_length = 5 + ((registers + 7) / 8); // bit-packed, ceil(n/8) bytes
else
- cmd.rx_length = 5 + (registers << 1); // 2 bytes per register
+ cmd.rx_length = 5 + (registers << 1); // 2 bytes per register
}
}
diff --git a/pin_bits_masks.h b/pin_bits_masks.h
index 3392213..6dba8dd 100644
--- a/pin_bits_masks.h
+++ b/pin_bits_masks.h
@@ -5,7 +5,7 @@
Part of grblHAL
- Copyright (c) 2021-2025 Terje Io
+ Copyright (c) 2021-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -49,7 +49,7 @@
#error "MPG mode input is not supported in this configuration!"
#endif
-#if QEI_SELECT_ENABLE && !defined(QEI_SELECT_PIN)
+#if (ENCODER_ENABLE & 1) && !defined(QEI_SELECT_PIN)
#error "Encoder select input is not supported in this configuration!"
#endif
@@ -157,12 +157,16 @@ static aux_ctrl_t aux_ctrl[] = {
#if MPG_ENABLE == 1 && defined(MPG_MODE_PIN)
add_aux_input(Input_MPGSelect, MPG_MODE, IRQ_Mode_Change, 0)
#endif
-#if QEI_SELECT_ENABLE && defined(QEI_SELECT_PIN)
+#if QEI_ENABLE && defined(QEI_A_PIN) && defined(QEI_B_PIN)
+ add_aux_input(Input_QEI_A, QEI_A, IRQ_Mode_Change, 0)
+ add_aux_input(Input_QEI_B, QEI_B, IRQ_Mode_Change, 0)
+#endif
+#if (ENCODER_ENABLE & 1) && defined(QEI_SELECT_PIN)
add_aux_input(Input_QEI_Select, QEI_SELECT, IRQ_Mode_RisingFalling, 0)
#endif
// Probe pins can be bound explicitly and can be "degraded" to not interrupt capable.
#if PROBE_ENABLE && defined(PROBE_PIN)
- add_aux_input(Input_Probe, PROBE, IRQ_Mode_RisingFalling, 0)
+ add_aux_input(Input_Probe, PROBE, IRQ_Mode_Change, 0)
#endif
#if PROBE2_ENABLE && defined(PROBE2_PIN)
add_aux_input(Input_Probe2, PROBE2, IRQ_Mode_RisingFalling, 0)
@@ -198,11 +202,19 @@ static aux_ctrl_t aux_ctrl[] = {
#endif
};
+
+// General inputs
+
static inline bool aux_ctrl_is_probe (pin_function_t function)
{
return function == Input_Probe || function == Input_Probe2 || function == Input_Toolsetter;
}
+static inline bool aux_ctrl_is_encoder (pin_function_t function)
+{
+ return function == Input_QEI_A || function == Input_QEI_B || function == Input_QEI_Select;
+}
+
#ifdef STM32_PLATFORM
static inline aux_ctrl_t *aux_ctrl_get_fn (aux_gpio_t gpio)
@@ -228,11 +240,10 @@ static inline xbar_t *aux_ctrl_claim_port (aux_ctrl_t *aux_ctrl)
if(aux_ctrl) {
if(aux_ctrl->port != IOPORT_UNASSIGNED && (pin = ioport_claim(Port_Digital, Port_Input, &aux_ctrl->port, NULL))) {
-
aux_ctrl->gpio.port = pin->port;
aux_ctrl->gpio.pin = pin->pin;
-
- ioport_set_function(pin, aux_ctrl->function, &aux_ctrl->signal);
+ if(ioport_set_function(pin, aux_ctrl->function, &aux_ctrl->signal))
+ pin->function = aux_ctrl->function;
} else
aux_ctrl->port = IOPORT_UNASSIGNED;
}
@@ -279,7 +290,7 @@ static inline void aux_ctrl_irq_enable (settings_t *settings, ioport_interrupt_c
if(idx) do {
if(aux_ctrl[--idx].port != 0xFF && aux_ctrl[idx].irq_mode != IRQ_Mode_None) {
- if(!aux_ctrl_is_probe(aux_ctrl[idx].function)) {
+ if(!(aux_ctrl_is_probe(aux_ctrl[idx].function) || aux_ctrl_is_encoder(aux_ctrl[idx].function))) {
pin_irq_mode_t irq_mode;
if((irq_mode = aux_ctrl[idx].irq_mode) & IRQ_Mode_RisingFalling)
irq_mode = (settings->control_invert.mask & aux_ctrl[idx].signal.mask) ? IRQ_Mode_Falling : IRQ_Mode_Rising;
@@ -560,7 +571,7 @@ static inline void aux_ctrl_claim_out_ports (aux_claim_explicit_out_ptr aux_clai
#endif
// IRQ enabled input singnals
-
+/*
#if QEI_ENABLE
#ifndef QEI_A_BIT
#define QEI_A_BIT (1<programmed_rate;
if(block->condition.rapid_motion)
- nominal_speed *= (0.01f * (float)sys.override.rapid_rate);
+ nominal_speed *= (0.01f * (float)pl.override.rapid_rate);
else {
- if(sys.override.feed_rate != 100 && !block->condition.no_feed_override) {
+ if(pl.override.feed_rate != 100 && !block->condition.no_feed_override) {
if(nominal_speed > block->rapid_rate)
nominal_speed = block->rapid_rate;
- nominal_speed *= (0.01f * (float)sys.override.feed_rate);
+ nominal_speed *= (0.01f * (float)pl.override.feed_rate);
}
if(nominal_speed > block->rapid_rate)
nominal_speed = block->rapid_rate;
@@ -741,13 +744,29 @@ void plan_sync_velocity (void *block)
}
// Set feed overrides
+FLASHMEM static void _plan_feed_override (override_t feed_rate, override_t rapid_rate)
+{
+ bool feedrate_changed, rapidrate_changed = false;
+
+ if(((feedrate_changed = feed_rate != pl.override.feed_rate) || (rapidrate_changed = rapid_rate != pl.override.rapid_rate))) {
+
+ pl.override.feed_rate = feed_rate;
+ pl.override.rapid_rate = rapid_rate;
+
+ if(plan_update_velocity_profile_parameters())
+ plan_cycle_reinitialize();
+
+ if(grbl.on_override_changed) {
+ if(feedrate_changed)
+ grbl.on_override_changed(OverrideChanged_FeedRate);
+ if(rapidrate_changed)
+ grbl.on_override_changed(OverrideChanged_RapidRate);
+ }
+ }
+}
+
FLASHMEM void plan_feed_override (override_t feed_override, override_t rapid_override)
{
- bool feedrate_changed = false, rapidrate_changed = false;
-
- if(sys.override.control.feed_rates_disable)
- return;
-
if(feed_override == 0)
feed_override = sys.override.feed_rate;
else
@@ -758,20 +777,18 @@ FLASHMEM void plan_feed_override (override_t feed_override, override_t rapid_ove
else
rapid_override = constrain(rapid_override, 5, 100);
- if((feedrate_changed = feed_override != sys.override.feed_rate) ||
- (rapidrate_changed = rapid_override != sys.override.rapid_rate)) {
+ if(feed_override != sys.override.feed_rate || rapid_override != sys.override.rapid_rate) {
+
sys.override.feed_rate = feed_override;
sys.override.rapid_rate = rapid_override;
+
report_add_realtime(Report_Overrides); // Set to report change immediately
- if(plan_update_velocity_profile_parameters())
- plan_cycle_reinitialize();
- if(grbl.on_override_changed) {
- if(feedrate_changed)
- grbl.on_override_changed(OverrideChanged_FeedRate);
- if(rapidrate_changed)
- grbl.on_override_changed(OverrideChanged_RapidRate);
- }
}
+
+ if(sys.override.control.feed_rates_disable)
+ _plan_feed_override(DEFAULT_FEED_OVERRIDE, DEFAULT_RAPID_OVERRIDE);
+ else
+ _plan_feed_override(sys.override.feed_rate, sys.override.rapid_rate);
}
FLASHMEM void plan_data_init (plan_line_data_t *plan_data)
@@ -779,6 +796,7 @@ FLASHMEM void plan_data_init (plan_line_data_t *plan_data)
memset(plan_data, 0, sizeof(plan_line_data_t));
plan_data->offset_id = gc_state.offset_id;
plan_data->spindle.hal = gc_spindle_get(-1)->hal;
+ plan_data->condition.no_feed_override = sys.override.control.feed_rates_disable;
plan_data->condition.target_validated = plan_data->condition.target_valid = sys.soft_limits.mask == 0;
#ifdef KINEMATICS_API
plan_data->rate_multiplier = 1.0f;
diff --git a/planner.h b/planner.h
index 5118058..b517ff7 100644
--- a/planner.h
+++ b/planner.h
@@ -3,7 +3,7 @@
Part of grblHAL
- Copyright (c) 2019-2025 Terje Io
+ Copyright (c) 2019-2026 Terje Io
Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
Copyright (c) 2009-2011 Simen Svale Skogsrud
@@ -162,6 +162,10 @@ typedef struct {
float previous_unit_vec[N_AXIS]; // Unit vector of previous path line segment
float previous_nominal_speed; // Nominal speed of previous path line segment
float actual_rpm; // Updated when in units per revolution blocks (G95) mode.
+ struct {
+ override_t feed_rate; //!< Feed rate override value in percent
+ override_t rapid_rate; //!< Rapids override value in percent
+ } override;
} planner_t;
// Initialize and reset the motion plan subsystem
diff --git a/plugins.h b/plugins.h
index 63c83a8..db468e0 100644
--- a/plugins.h
+++ b/plugins.h
@@ -7,7 +7,7 @@
Part of grblHAL
- Copyright (c) 2020-2025 Terje Io
+ Copyright (c) 2020-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -154,25 +154,7 @@ typedef struct {
uint8_t id;
} modbus_tcp_settings_t;
-// Quadrature encoder interface
-
-typedef enum {
- Encoder_Universal = 0,
- Encoder_FeedRate,
- Encoder_RapidRate,
- Encoder_Spindle_RPM,
- Encoder_MPG,
- Encoder_MPG_X,
- Encoder_MPG_Y,
- Encoder_MPG_Z,
- Encoder_MPG_A,
- Encoder_MPG_B,
- Encoder_MPG_C,
- Encoder_MPG_U,
- Encoder_MPG_V,
- Encoder_MPG_W,
- Encoder_Spindle_Position
-} encoder_mode_t;
+// Encoder settings offsets
typedef enum {
Setting_EncoderMode = 0,
@@ -181,45 +163,6 @@ typedef enum {
Setting_EncoderDblClickWindow = 3 // ms
} encoder_setting_id_t;
-typedef union {
- uint8_t events;
- struct {
- uint8_t position_changed :1,
- direction_changed :1,
- click :1,
- dbl_click :1,
- long_click :1,
- index_pulse :1,
- unused :2;
- };
-} encoder_event_t;
-
-typedef union {
- uint8_t flags;
- uint8_t value;
- struct {
- uint8_t single_count_per_detent :1;
- };
-} encoder_flags_t;
-
-typedef struct {
- encoder_mode_t mode;
- uint32_t cpr; //!< Count per revolution.
- uint32_t cpd; //!< Count per detent.
- uint32_t dbl_click_window; //!< ms.
- encoder_flags_t flags;
-} encoder_settings_t;
-
-typedef struct {
- encoder_mode_t mode;
- uint_fast8_t id;
- uint_fast8_t axis; //!< Axis index for MPG encoders, 0xFF for others.
- int32_t position;
- uint32_t velocity;
- encoder_event_t event;
- encoder_settings_t *settings;
-} encoder_t;
-
// DISPLAYS:
// Interfaces
diff --git a/plugins_init.h b/plugins_init.h
index cd66bd0..17d0ad1 100644
--- a/plugins_init.h
+++ b/plugins_init.h
@@ -7,7 +7,7 @@
Part of grblHAL
- Copyright (c) 2021-2025 Terje Io
+ Copyright (c) 2021-2026 Terje Io
grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -181,6 +181,11 @@
homing_pulloff_init();
#endif
+#if ENCODER_ENABLE
+ extern bool encoder_init (void);
+ encoder_init();
+#endif
+
extern void my_plugin_init (void);
my_plugin_init();
diff --git a/settings.c b/settings.c
index d80e4d7..d379008 100644
--- a/settings.c
+++ b/settings.c
@@ -29,10 +29,12 @@
#include "hal.h"
#include "config.h"
+#include "encoders.h"
#include "machine_limits.h"
#include "nvs_buffer.h"
#include "tool_change.h"
#include "state_machine.h"
+#include "strutils.h"
#if ENABLE_BACKLASH_COMPENSATION
#include "motion_control.h"
#endif
@@ -738,17 +740,26 @@ static status_code_t set_pwm_mode (setting_id_t id, uint_fast16_t int_value)
static status_code_t set_pwm_options (setting_id_t id, uint_fast16_t int_value)
{
if(int_value & 0b0001) {
- if(int_value > 0b1111)
+#if N_SPINDLE > 1
+ if(int_value > 0b11111)
return Status_SettingValueOutOfRange;
+#else
+ if(int_value > 0b01111)
+ return Status_SettingValueOutOfRange;
+#endif
settings.pwm_spindle.flags.pwm_disable = Off;
- settings.pwm_spindle.flags.enable_rpm_controlled = !!(int_value & 0b0010);
- settings.pwm_spindle.flags.laser_mode_disable = !!(int_value & 0b0100);
- settings.pwm_spindle.flags.pwm_ramped = !!(int_value & 0b1000);
+ settings.pwm_spindle.flags.enable_rpm_controlled = !!(int_value & 0b00010);
+ settings.pwm_spindle.flags.laser_mode_disable = !!(int_value & 0b00100);
+ settings.pwm_spindle.flags.pwm_ramped = !!(int_value & 0b01000);
+#if N_SPINDLE > 1
+ settings.pwm_spindle.flags.ignore_delays = !!(int_value & 0b10000);
+#endif
} else {
settings.pwm_spindle.flags.pwm_disable = On;
settings.pwm_spindle.flags.enable_rpm_controlled =
settings.pwm_spindle.flags.laser_mode_disable =
- settings.pwm_spindle.flags.pwm_ramped = Off;
+ settings.pwm_spindle.flags.pwm_ramped =
+ settings.pwm_spindle.flags.ignore_delays = Off;
}
return Status_OK;
@@ -1520,9 +1531,10 @@ FLASHMEM static uint32_t get_int (setting_id_t id)
value = settings.pwm_spindle.flags.pwm_disable
? 0
: (0b0001 |
- (settings.pwm_spindle.flags.enable_rpm_controlled ? 0b0010 : 0) |
- (settings.pwm_spindle.flags.laser_mode_disable ? 0b0100 : 0) |
- (settings.pwm_spindle.flags.pwm_ramped ? 0b1000 : 0));
+ (settings.pwm_spindle.flags.enable_rpm_controlled ? 0b00010 : 0) |
+ (settings.pwm_spindle.flags.laser_mode_disable ? 0b00100 : 0) |
+ (settings.pwm_spindle.flags.pwm_ramped ? 0b01000 : 0) |
+ (settings.pwm_spindle.flags.ignore_delays ? 0b10000 : 0));
break;
case Setting_Mode:
@@ -2068,7 +2080,7 @@ PROGMEM static const setting_detail_t setting_detail[] = {
{ Setting_InvertProbePin, Group_Probing, "Invert probe inputs", NULL, Format_Bitfield, probe_signals, NULL, NULL, Setting_IsLegacyFn, set_probe_invert, get_int, is_setting_available },
{ Setting_SpindlePWMBehaviour, Group_Spindle, "Deprecated", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsLegacyFn, set_pwm_mode, get_int, is_setting_available },
{ Setting_GangedDirInvertMask, Group_Stepper, "Ganged axes direction invert", NULL, Format_Bitfield, ganged_axes, NULL, NULL, Setting_IsExtendedFn, set_ganged_dir_invert, get_int, is_setting_available },
- { Setting_SpindlePWMOptions, Group_Spindle, "PWM spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability,Enable ramping", NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, is_setting_available },
+ { Setting_SpindlePWMOptions, Group_Spindle, "PWM spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability,Enable ramping" PWM_SPINDLE_NO_DELAYS, NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, is_setting_available },
#if COMPATIBILITY_LEVEL <= 1
{ Setting_StatusReportMask, Group_General, "Status report options", NULL, Format_Bitfield, "Position in machine coordinate,Buffer state,Line numbers,Feed & speed,Pin state,Work coordinate offset,Overrides,Probe coordinates,Buffer sync on WCO change,Parser state,Alarm substatus,Run substatus,Enable when homing,Distance-to-go", NULL, NULL, Setting_IsExtendedFn, set_report_mask, get_int, NULL },
#else
@@ -2685,6 +2697,10 @@ FLASHMEM static void sanity_check (void)
settings.steppers.rotary_wrap.mask &= settings.steppers.is_rotary.mask;
#endif
+#if N_SPINDLE == 1
+ settings.pwm_spindle.flags.ignore_delays = Off;
+#endif
+
settings.control_invert.mask |= limits_override.mask;
settings.control_disable_pullup.mask &= ~limits_override.mask;
}
@@ -2906,7 +2922,7 @@ static inline const setting_detail_t *_setting_get_details (setting_id_t id, uin
if(details->settings[idx].group == Group_Axis0 && grbl.on_set_axis_setting_unit)
set_axis_unit(&details->settings[idx], grbl.on_set_axis_setting_unit(details->settings[idx].id, offset));
- if(offset && details->iterator == NULL && offset >= (details->settings[idx].group == Group_Encoder0 ? hal.encoder.get_n_encoders() : N_AXIS))
+ if(offset && details->iterator == NULL && offset >= (details->settings[idx].group == Group_Encoder0 ? encoders_get_count() : N_AXIS))
return NULL;
if(set)
@@ -3062,22 +3078,6 @@ FLASHMEM static status_code_t validate_uint_value (const setting_detail_t *setti
return Status_OK;
}
-FLASHMEM static uint32_t strnumentries (const char *s, const char delimiter)
-{
- if(s == NULL || *s == '\0')
- return 0;
-
- char *p = (char *)s;
- uint32_t entries = 1;
-
- while((p = strchr(p, delimiter))) {
- p++;
- entries++;
- }
-
- return entries;
-}
-
FLASHMEM setting_datatype_t setting_datatype_to_external (setting_datatype_t datatype)
{
switch(datatype) {
diff --git a/spindle_control.c b/spindle_control.c
index e5f3f7a..a2497cf 100644
--- a/spindle_control.c
+++ b/spindle_control.c
@@ -117,9 +117,14 @@ FLASHMEM static bool spindle_activate (spindle_id_t spindle_id, spindle_num_t sp
sys_spindle[spindle_num].param.hal = &sys_spindle[spindle_num].hal;
if(sys_spindle[spindle_num].param.override_pct == 0)
sys_spindle[spindle_num].param.override_pct = DEFAULT_SPINDLE_RPM_OVERRIDE;
- if(spindle_hal.type == SpindleType_PWM && !spindle_hal.cap.laser && spindle_hal.context.pwm->flags.ramp_pwm ) {
- sys_spindle[spindle_num].param.ramp_up = settings.spindle.on_delay > 0;
- sys_spindle[spindle_num].param.ramp_down = settings.spindle.off_delay > 0;
+ sys_spindle[spindle_num].param.option.ramp_up = sys_spindle[spindle_num].param.option.ramp_down = sys_spindle[spindle_num].param.option.ignore_delays = Off;
+ if(spindle_hal.type == SpindleType_PWM) {
+ if(!spindle_hal.cap.laser && spindle_hal.context.pwm->flags.ramp_pwm ) {
+ sys_spindle[spindle_num].param.option.ramp_up = settings.spindle.on_delay > 0;
+ sys_spindle[spindle_num].param.option.ramp_down = settings.spindle.off_delay > 0;
+ }
+ if(!sys_spindle[spindle_num].param.option.ramp_up)
+ sys_spindle[spindle_num].param.option.ignore_delays = spindle_hal.context.pwm->settings->flags.ignore_delays;
}
spindle_hal.param = &sys_spindle[spindle_num].param;
memcpy(&sys_spindle[spindle_num].hal, &spindle_hal, sizeof(spindle_ptrs_t));
@@ -291,11 +296,12 @@ FLASHMEM void spindle_update_caps (spindle_ptrs_t *spindle, spindle_pwm_t *pwm_c
sys_spindle[idx].hal.rpm_max = spindle->rpm_max;
sys_spindle[idx].hal.pwm_off_value = spindle->pwm_off_value;
if((sys_spindle[idx].hal.cap.laser = spindle->cap.laser) || spindle->type != SpindleType_PWM || !spindle->context.pwm->flags.ramp_pwm)
- sys_spindle[idx].param.ramp_up = sys_spindle[idx].param.ramp_down = Off;
+ sys_spindle[idx].param.option.ramp_up = sys_spindle[idx].param.option.ramp_down = Off;
else {
- sys_spindle[idx].param.ramp_up = settings.spindle.on_delay > 0;
- sys_spindle[idx].param.ramp_down = settings.spindle.off_delay > 0;
+ sys_spindle[idx].param.option.ramp_up = settings.spindle.on_delay > 0;
+ sys_spindle[idx].param.option.ramp_down = settings.spindle.off_delay > 0;
}
+ sys_spindle[idx].param.option.ignore_delays = spindle->type == SpindleType_PWM && !sys_spindle[idx].param.option.ramp_up && spindle->context.pwm->settings->flags.ignore_delays;
break;
}
} while(idx);
@@ -609,37 +615,33 @@ __NOTE:__ Unlike motion overrides, spindle overrides do not require a planner re
*/
FLASHMEM float spindle_set_override (spindle_ptrs_t *spindle, override_t speed_override)
{
- if(speed_override == DEFAULT_SPINDLE_RPM_OVERRIDE || !spindle->param->state.override_disable) {
+ speed_override = speed_override == 0 ? spindle->param->override_pct : constrain(speed_override, MIN_SPINDLE_RPM_OVERRIDE, MAX_SPINDLE_RPM_OVERRIDE);
- speed_override = constrain(speed_override, MIN_SPINDLE_RPM_OVERRIDE, MAX_SPINDLE_RPM_OVERRIDE);
+ if((uint8_t)speed_override != spindle->param->override_pct || spindle->param->state.override_disable != spindle->param->option.override_disable) {
- if((uint8_t)speed_override != spindle->param->override_pct) {
+ float rpm = spindle->param->rpm_overridden;
- float rpm = spindle->param->rpm_overridden;
- sys_state_t state = state_get();
+ spindle_set_rpm(spindle, spindle->param->rpm, speed_override);
- spindle_set_rpm(spindle, spindle->param->rpm, speed_override);
-
- if(!(spindle->param->ramp_up &&
- spindle->get_state(spindle).on &&
- spindle_ramp_override(spindle, rpm, spindle->param->rpm_overridden))) {
- if(state == STATE_IDLE) {
- if(spindle->get_pwm && spindle->update_pwm)
- spindle->update_pwm(spindle, spindle->get_pwm(spindle, spindle->param->rpm_overridden));
- else if(spindle->update_rpm)
- spindle->update_rpm(spindle, spindle->param->rpm_overridden);
- } else
- sys.step_control.update_spindle_rpm = On;
- }
-
- report_add_realtime(Report_Overrides); // Set to report change immediately
-
- // if(grbl.on_spindle_programmed)
- // grbl.on_spindle_programmed(spindle, spindle->param->state, spindle->param->rpm, spindle->param->rpm_mode);
-
- if(grbl.on_override_changed)
- grbl.on_override_changed(OverrideChanged_SpindleRPM);
+ if(!(spindle->param->option.ramp_up &&
+ spindle->get_state(spindle).on &&
+ spindle_ramp_override(spindle, rpm, spindle->param->rpm_overridden))) {
+ if(state_get() == STATE_IDLE) {
+ if(spindle->get_pwm && spindle->update_pwm)
+ spindle->update_pwm(spindle, spindle->get_pwm(spindle, spindle->param->rpm_overridden));
+ else if(spindle->update_rpm)
+ spindle->update_rpm(spindle, spindle->param->rpm_overridden);
+ } else
+ sys.step_control.update_spindle_rpm = On;
}
+
+ report_add_realtime(Report_Overrides); // Set to report change immediately
+
+// if(grbl.on_spindle_programmed)
+// grbl.on_spindle_programmed(spindle, spindle->param->state, spindle->param->rpm, spindle->param->rpm_mode);
+
+ if(grbl.on_override_changed)
+ grbl.on_override_changed(OverrideChanged_SpindleRPM);
}
return spindle->param->rpm_overridden;
@@ -647,10 +649,8 @@ FLASHMEM float spindle_set_override (spindle_ptrs_t *spindle, override_t speed_o
FLASHMEM bool spindle_override_disable (spindle_ptrs_t *spindle, bool disable)
{
- if(disable && !spindle->param->state.override_disable)
- spindle_set_override(spindle, DEFAULT_SPINDLE_RPM_OVERRIDE);
-
- spindle->param->state.override_disable = disable;
+ spindle->param->option.override_disable = disable;
+ spindle_set_override(spindle, spindle->param->override_pct);
return disable;
}
@@ -723,7 +723,7 @@ FLASHMEM static bool _spindle_set_state (spindle_ptrs_t *spindle, spindle_state_
if(!state.on) { // Halt or set spindle direction and rpm.
rpm = 0.0f;
- if(spindle->param->ramp_down)
+ if(spindle->param->option.ramp_down)
spindle_ramp(spindle, spindle->param->state, spindle->param->rpm_overridden, 0.0f, settings.spindle.off_delay);
spindle->set_state(spindle, (spindle_state_t){0}, (spindle->param->rpm_overridden = 0.0f));
} else {
@@ -732,7 +732,7 @@ FLASHMEM static bool _spindle_set_state (spindle_ptrs_t *spindle, spindle_state_
if(spindle->cap.laser && state.ccw)
rpm = 0.0f; // TODO: May need to be rpm_min*(100/MAX_SPINDLE_RPM_OVERRIDE);
- if(spindle->param->ramp_up) {
+ if(spindle->param->option.ramp_up) {
if(spindle->param->state.on && spindle->param->state.ccw != state.ccw) {
spindle_ramp(spindle, spindle->param->state, spindle->param->rpm_overridden, 0.0f, settings.spindle.off_delay);
spindle->param->rpm = spindle->param->rpm_overridden = 0.0f;
@@ -775,7 +775,9 @@ FLASHMEM static bool spindle_set_state_wait (spindle_ptrs_t *spindle, spindle_st
if((ok = _spindle_set_state(spindle, state, rpm, delay_ms))) {
- if(sys.override.control.spindle_wait_disable || (state.on ? spindle->param->ramp_up : spindle->param->ramp_down)) {
+ if(sys.override.control.spindle_wait_disable ||
+ spindle->param->option.ignore_delays ||
+ (state.on ? spindle->param->option.ramp_up : spindle->param->option.ramp_down)) {
sys.override.control.spindle_wait_disable = Off;
} else {
@@ -859,15 +861,16 @@ FLASHMEM bool spindle_restore (spindle_ptrs_t *spindle, spindle_state_t state, f
*/
float spindle_set_rpm (spindle_ptrs_t *spindle, float rpm, override_t override_pct)
{
- if(override_pct != 100)
+ if(override_pct != 100 && !spindle->param->option.override_disable)
rpm *= 0.01f * (float)override_pct; // Scale RPM by override value.
rpm = rpm <= 0.0f ? 0.0f : constrain(rpm, spindle->rpm_min, spindle->rpm_max);
spindle->param->rpm_overridden = rpm;
spindle->param->override_pct = override_pct;
+ spindle->param->state.override_disable = spindle->param->option.override_disable;
- return spindle->param->rpm_overridden;
+ return rpm;
}
/*! \brief Turn off all enabled spindles.
@@ -880,7 +883,7 @@ FLASHMEM void spindle_all_off (bool reset)
do {
if((spindle = spindle_get(--spindle_num))) {
- if(!reset && spindle->param->ramp_down)
+ if(!reset && spindle->param->option.ramp_down)
spindle_set_state(spindle, (spindle_state_t){0}, 0.0f);
spindle->param->rpm = spindle->param->rpm_overridden = 0.0f;
@@ -1118,17 +1121,26 @@ FLASHMEM static status_code_t set_spindle_invert (setting_id_t id, uint_fast16_t
FLASHMEM static status_code_t set_pwm_options (setting_id_t id, uint_fast16_t int_value)
{
if(int_value & 0b0001) {
- if(int_value > 0b1111)
+#if N_SPINDLE > 1
+ if(int_value > 0b11111)
return Status_SettingValueOutOfRange;
+#else
+ if(int_value > 0b01111)
+ return Status_SettingValueOutOfRange;
+#endif
sp1_settings.cfg.flags.pwm_disable = Off;
- sp1_settings.cfg.flags.enable_rpm_controlled = !!(int_value & 0b0010);
- sp1_settings.cfg.flags.laser_mode_disable = !!(int_value & 0b0100);
- sp1_settings.cfg.flags.pwm_ramped = !!(int_value & 0b1000);
- } else {
+ sp1_settings.cfg.flags.enable_rpm_controlled = !!(int_value & 0b00010);
+ sp1_settings.cfg.flags.laser_mode_disable = !!(int_value & 0b00100);
+ sp1_settings.cfg.flags.pwm_ramped = !!(int_value & 0b01000);
+#if N_SPINDLE > 1
+ sp1_settings.cfg.flags.ignore_delays = !!(int_value & 0b10000);
+#endif
+ } else {
sp1_settings.cfg.flags.pwm_disable = On;
sp1_settings.cfg.flags.enable_rpm_controlled =
sp1_settings.cfg.flags.laser_mode_disable =
- sp1_settings.cfg.flags.pwm_ramped = Off;
+ sp1_settings.cfg.flags.pwm_ramped =
+ sp1_settings.cfg.flags.ignore_delays = Off;
}
return Status_OK;
@@ -1144,9 +1156,10 @@ FLASHMEM static uint32_t get_int (setting_id_t id)
value = sp1_settings.cfg.flags.pwm_disable
? 0
: (0b0001 |
- (sp1_settings.cfg.flags.enable_rpm_controlled ? 0b0010 : 0) |
- (sp1_settings.cfg.flags.laser_mode_disable ? 0b0100 : 0) |
- (sp1_settings.cfg.flags.pwm_ramped ? 0b1000 : 0));
+ (sp1_settings.cfg.flags.enable_rpm_controlled ? 0b00010 : 0) |
+ (sp1_settings.cfg.flags.laser_mode_disable ? 0b00100 : 0) |
+ (sp1_settings.cfg.flags.pwm_ramped ? 0b01000 : 0) |
+ (sp1_settings.cfg.flags.ignore_delays ? 0b10000 : 0));
break;
case Setting_SpindleInvertMask1:
@@ -1232,7 +1245,7 @@ PROGMEM static const setting_detail_t spindle1_settings[] = {
{ Setting_Spindle_DirPort, Group_AuxPorts, "PWM2 spindle direction port", NULL, Format_Decimal, "-#0", "-1", max_dport, Setting_NonCoreFn, set_port, get_port, has_ports, { .reboot_required = On } },
{ Setting_SpindleInvertMask1, Group_Spindle, "PWM2 spindle signals invert", NULL, Format_Bitfield, spindle_signals, NULL, NULL, Setting_IsExtendedFn, set_spindle_invert, get_int, NULL, { .reboot_required = On } },
{ Setting_Spindle_PWMPort, Group_AuxPorts, "PWM2 spindle PWM port", NULL, Format_Decimal, "-#0", "0", max_aport, Setting_NonCoreFn, set_port, get_port, has_ports, { .reboot_required = On } },
- { Setting_SpindlePWMOptions1, Group_Spindle, "PWM2 spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability,Enable ramping", NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, has_pwm },
+ { Setting_SpindlePWMOptions1, Group_Spindle, "PWM2 spindle options", NULL, Format_XBitfield, "Enable,RPM controls spindle enable signal,Disable laser mode capability,Enable ramping" PWM_SPINDLE_NO_DELAYS, NULL, NULL, Setting_IsExtendedFn, set_pwm_options, get_int, has_pwm },
{ Setting_RpmMax1, Group_Spindle, "PWM2 spindle max speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_max, NULL, has_pwm },
{ Setting_RpmMin1, Group_Spindle, "PWM2 spindle min speed", "RPM", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsLegacy, &sp1_settings.cfg.rpm_min, NULL, has_pwm },
{ Setting_PWMFreq1, Group_Spindle, "PWM2 spindle PWM frequency", "Hz", Format_Decimal, "#####0", NULL, NULL, Setting_IsExtended, &sp1_settings.cfg.pwm_freq, NULL, has_freq },
@@ -1363,6 +1376,10 @@ FLASHMEM static void spindle1_settings_load (void)
{
if((hal.nvs.memcpy_from_nvs((uint8_t *)&sp1_settings, nvs_address, sizeof(spindle1_pwm_settings_t), true) != NVS_TransferResult_OK))
spindle1_settings_restore();
+
+#if N_SPINDLE == 1
+ sp1_settings.cfg.flags.ignore_delays = Off;
+#endif
}
FLASHMEM spindle1_pwm_settings_t *spindle1_settings_add (bool claim_ports)
diff --git a/spindle_control.h b/spindle_control.h
index 74460f8..fde5cc6 100644
--- a/spindle_control.h
+++ b/spindle_control.h
@@ -3,7 +3,7 @@
Part of grblHAL
- Copyright (c) 2017-2025 Terje Io
+ Copyright (c) 2017-2026 Terje Io
Copyright (c) 2012-2015 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud
@@ -220,10 +220,18 @@ typedef union {
pwm_disable :1, // PWM spindle only
g92offset :1,
pwm_ramped :1, // PWM spindle only
- unassigned :3;
+ ignore_delays :1, // PWM spindle only
+ unassigned :2;
};
} spindle_settings_flags_t;
+#if N_SPINDLE == 1
+#define PWM_SPINDLE_NO_DELAYS
+#else
+#define PWM_SPINDLE_NO_DELAYS ",Ignore on/off delays"
+#endif
+
+
typedef union {
uint8_t value;
uint8_t mask;
@@ -348,8 +356,12 @@ typedef struct spindle_param {
spindle_state_t state;
override_t override_pct; //!< Spindle RPM override value in percent
spindle_css_data_t css; //!< Data used for Constant Surface Speed Mode (CSS) calculations, NULL if not in CSS mode.
- bool ramp_up;
- bool ramp_down;
+ struct {
+ uint8_t ramp_up :1,
+ ramp_down :1,
+ ignore_delays :1,
+ override_disable :1;
+ } option;
spindle_ptrs_t *hal;
} spindle_param_t;
diff --git a/state_machine.c b/state_machine.c
index 5b9d87e..29c7579 100644
--- a/state_machine.c
+++ b/state_machine.c
@@ -30,6 +30,8 @@
#include "state_machine.h"
#include "override.h"
+extern void gc_tool_changed (void);
+
static void state_idle (uint_fast16_t new_state);
static void state_cycle (uint_fast16_t rt_exec);
static void state_await_hold (uint_fast16_t rt_exec);
@@ -456,16 +458,13 @@ static void state_cycle (uint_fast16_t rt_exec)
*/
FLASHMEM static void state_await_toolchanged (uint_fast16_t rt_exec)
{
- if (rt_exec & EXEC_CYCLE_START) {
- if (!gc_state.tool_change) {
+ if(rt_exec & EXEC_CYCLE_START) {
+ if(!gc_state.tool_change) {
- if (hal.stream.suspend_read)
+ if(hal.stream.suspend_read)
hal.stream.suspend_read(false); // Tool change complete, restore "normal" stream input.
- if(grbl.on_tool_changed)
- grbl.on_tool_changed(gc_state.tool);
-
- report_add_realtime(Report_Tool);
+ gc_tool_changed();
}
pending_state = gc_state.tool_change ? STATE_TOOL_CHANGE : STATE_IDLE;
state_set(STATE_IDLE);
diff --git a/strutils.c b/strutils.c
index 050e24f..8b90e90 100644
--- a/strutils.c
+++ b/strutils.c
@@ -25,16 +25,15 @@
#include
#include
+#include "grbl.h"
#include "strutils.h"
-#define CAPS(c) ((c >= 'a' && c <= 'z') ? c & 0x5F : c)
-
/*! \brief Case insensitive search for first occurrence of a string inside another.
\param s1 pointer to string to search.
\param s2 pointer to string to search for.
\returns pointer to found string or NULL if not found.
*/
-char *stristr (const char *s1, const char *s2)
+FLASHMEM char *stristr (const char *s1, const char *s2)
{
const char *s = s1, *p = s2, *r = NULL;
@@ -69,7 +68,7 @@ char *stristr (const char *s1, const char *s2)
\param len max. number of characters to compare.
\returns pointer to found string or NULL if not found.
*/
-char *strnistr (const char *s1, const char *s2, size_t len)
+FLASHMEM char *strnistr (const char *s1, const char *s2, size_t len)
{
size_t slen = len;
const char *s = s1, *p = s2, *r = NULL;
@@ -102,8 +101,9 @@ char *strnistr (const char *s1, const char *s2, size_t len)
return slen == 0 || *p == '\0' ? (char *)r : NULL;
}
-// NOTE: ensure buf is large enough to hold concatenated strings!
-char *strappend (char *buf, int argc, ...)
+// NOTE: Ensure buf is large enough to hold concatenated strings!
+// Do NOT use for several int/float conversions as these share the same underlying buffer!
+FLASHMEM char *strappend (char *buf, int argc, ...)
{
char c, *s = buf, *arg;
@@ -123,10 +123,10 @@ char *strappend (char *buf, int argc, ...)
return buf;
}
-uint32_t strnumentries (const char *s, const char delimiter)
+FLASHMEM uint32_t strnumentries (const char *s, const char delimiter)
{
char *p = (char *)s;
- uint32_t entries = *s ? 1 : 0;
+ uint32_t entries = (s && *s) ? 1 : 0;
while(entries && (p = strchr(p, delimiter))) {
p++;
@@ -136,7 +136,7 @@ uint32_t strnumentries (const char *s, const char delimiter)
return entries;
}
-char *strgetentry (char *res, const char *s, uint32_t entry, const char delimiter)
+FLASHMEM char *strgetentry (char *res, const char *s, uint32_t entry, const char delimiter)
{
char *e, *p = (char *)s;
@@ -163,7 +163,7 @@ char *strgetentry (char *res, const char *s, uint32_t entry, const char delimite
return res;
}
-int32_t strlookup (const char *s1, const char *s2, const char delimiter)
+FLASHMEM int32_t strlookup (const char *s1, const char *s2, const char delimiter)
{
bool found = false;
char *e, *p = (char *)s2;
@@ -188,7 +188,7 @@ int32_t strlookup (const char *s1, const char *s2, const char delimiter)
return found ? entry : -1;
}
-bool strtotime (char *s, struct tm *time)
+FLASHMEM bool strtotime (char *s, struct tm *time)
{
char c, *s1 = s;
uint_fast16_t idx = 0;
@@ -285,7 +285,7 @@ bool strtotime (char *s, struct tm *time)
return idx >= 5;
}
-char *strtoisodt (struct tm *dt)
+FLASHMEM char *strtoisodt (struct tm *dt)
{
static char buf[21];
@@ -295,9 +295,9 @@ char *strtoisodt (struct tm *dt)
return buf;
}
-char *strtointernetdt (struct tm *dt)
+FLASHMEM char *strtointernetdt (struct tm *dt)
{
- static const char *month_table[12] = {
+ PROGMEM static const char *month_table[12] = {
"Jan",
"Feb",
"Mar",
@@ -312,7 +312,7 @@ char *strtointernetdt (struct tm *dt)
"Dec"
};
- static const char *day_table[7] = {
+ PROGMEM static const char *day_table[7] = {
"Sun",
"Mon",
"Tue",
diff --git a/tool_change.c b/tool_change.c
index e125ff9..4235853 100644
--- a/tool_change.c
+++ b/tool_change.c
@@ -115,23 +115,10 @@ FLASHMEM static void change_completed (void)
#endif
}
-// Reset claimed HAL entry points and restore previous tool if needed on soft restart.
+// Reset claimed HAL entry points on soft restart.
// Called from EXEC_RESET and EXEC_STOP handlers (via HAL).
FLASHMEM static void reset (void)
{
- if(next_tool) { //TODO: move to gc_xxx() function?
- // Restore previous tool if reset is during change
- if(current_tool.tool_id != next_tool->tool_id) {
- if(grbl.tool_table.n_tools)
- memcpy(gc_state.tool, ¤t_tool, sizeof(tool_data_t));
- else
- memcpy(next_tool, ¤t_tool, sizeof(tool_data_t));
- report_add_realtime(Report_Tool);
- }
- gc_state.tool_pending = gc_state.tool->tool_id;
- next_tool = NULL;
- }
-
change_completed();
driver_reset();
}