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