From ce7c3592b45fe9b5b1909f22cced15201bc72da6 Mon Sep 17 00:00:00 2001 From: Terje Io Date: Mon, 29 May 2023 00:46:05 +0200 Subject: [PATCH] Added experimental support for program flow control, mainly available for use in G65 macros stored on SD card or in littlefs. --- CMakeLists.txt | 1 + README.md | 2 +- changelog.md | 12 ++ errors.c | 8 +- errors.h | 5 + gcode.c | 21 ++- gcode.h | 1 + grbl.h | 2 +- ngc_flowctrl.c | 417 +++++++++++++++++++++++++++++++++++++++++++++++++ ngc_flowctrl.h | 30 ++++ system.h | 8 +- 11 files changed, 502 insertions(+), 5 deletions(-) create mode 100644 ngc_flowctrl.c create mode 100644 ngc_flowctrl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f81c594..e959b07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ target_sources(grbl INTERFACE ${CMAKE_CURRENT_LIST_DIR}/errors.c ${CMAKE_CURRENT_LIST_DIR}/ngc_params.c ${CMAKE_CURRENT_LIST_DIR}/ngc_expr.c + ${CMAKE_CURRENT_LIST_DIR}/ngc_flowctrl.c ${CMAKE_CURRENT_LIST_DIR}/regex.c ${CMAKE_CURRENT_LIST_DIR}/ioports.c ${CMAKE_CURRENT_LIST_DIR}/vfs.c diff --git a/README.md b/README.md index c729f19..3d20e6f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ It has been written to complement grblHAL and has features such as proper keyboa --- -Latest build date is 20230519, see the [changelog](changelog.md) for details. +Latest build date is 20230529, see the [changelog](changelog.md) for details. __NOTE:__ A settings reset will be performed on an update of builds earlier than 20230125. Backup and restore of settings is recommended. __IMPORTANT!__ A new setting has been introduced for ganged axes motors in build 20211121. I have only bench tested this for a couple of drivers, correct function should be verified after updating by those who have more than three motors configured. diff --git a/changelog.md b/changelog.md index 6c7176e..e8a6e6a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,17 @@ ## grblHAL changelog +Build 20230529 + +Core: + +* Added experimental support for [program flow control](https://github.com/grblHAL/core/discussions/309), mainly available for use in [G65 macros](https://github.com/grblHAL/core/wiki/Additional-G--and-M-codes#codes-available-if-driver-or-plugins-supports-them) stored on SD card or in littlefs. + +Drivers: + +* STM32F4xx: fix for [issue # 116](https://github.com/grblHAL/STM32F4xx/issues/116#issuecomment-1565369604), incorrect spindle RPM reported from encoder. + +-- + Build 20230526 Core: diff --git a/errors.c b/errors.c index eaa6978..4ea3223 100644 --- a/errors.c +++ b/errors.c @@ -93,7 +93,13 @@ PROGMEM static const status_detail_t status_detail[] = { #endif { Status_AuthenticationRequired, "Authentication required." }, { Status_AccessDenied, "Access denied." }, - { Status_NotAllowedCriticalEvent, "Not allowed while critical event is active." } + { Status_NotAllowedCriticalEvent, "Not allowed while critical event is active." }, +#if NGC_EXPRESSIONS_ENABLE + { Status_FlowControlNotExecutingMacro, "Flow statement only allowed in filesystem macro." }, + { Status_FlowControlSyntaxError, "Unknown flow statement." }, + { Status_FlowControlStackOverflow, "Stack overflow while executing flow statement." }, + { Status_FlowControlOutOfMemory, "Out of memory while executing flow statement." } +#endif }; static error_details_t details = { diff --git a/errors.h b/errors.h index 93de049..efe098b 100644 --- a/errors.h +++ b/errors.h @@ -107,6 +107,11 @@ typedef enum { Status_AccessDenied = 78, Status_NotAllowedCriticalEvent = 79, + Status_FlowControlNotExecutingMacro = 80, + Status_FlowControlSyntaxError = 81, + Status_FlowControlStackOverflow = 82, + Status_FlowControlOutOfMemory = 83, + Status_Unhandled, // For internal use only Status_StatusMax = Status_Unhandled } __attribute__ ((__packed__)) status_code_t; diff --git a/gcode.c b/gcode.c index ed60fb8..c2c181d 100644 --- a/gcode.c +++ b/gcode.c @@ -33,6 +33,7 @@ #if NGC_EXPRESSIONS_ENABLE #include "ngc_expr.h" #include "ngc_params.h" +#include "ngc_flowctrl.h" #endif // NOTE: Max line number is defined by the g-code standard to be 99999. It seems to be an @@ -458,7 +459,7 @@ char *gc_normalize_block (char *block, char **message) break; case ')': - if(comment) { + if(comment && !gc_state.skip_blocks) { *s1 = '\0'; if(!hal.driver_cap.no_gcode_message_handling) { size_t len = s1 - comment - 4; @@ -543,6 +544,8 @@ static status_code_t read_parameter (char *line, uint_fast8_t *char_counter, flo #endif +extern status_code_t ngc_parse_command (uint32_t o_label, char *line, uint_fast8_t *pos, bool *skip); + // Parses and executes one block (line) of 0-terminated G-Code. // In this function, all units and positions are converted and exported to internal functions // in terms of (mm, mm/min) and absolute machine coordinates, respectively. @@ -600,6 +603,10 @@ status_code_t gc_execute_block (char *block) #if NGC_EXPRESSIONS_ENABLE + static const parameter_words_t o_label = { + .o = On + }; + uint_fast8_t ngc_param_count = 0; // NOTE: this array has to match the parameter_words_t order! @@ -776,12 +783,20 @@ status_code_t gc_execute_block (char *block) continue; } + if((gc_block.words.mask & o_label.mask) && (gc_block.words.mask & ~o_label.mask) == 0) { + char_counter--; + return ngc_flowctrl(gc_block.values.o, block, &char_counter, &gc_state.skip_blocks); + } + if((letter < 'A' && letter != '$') || letter > 'Z') FAIL(Status_ExpectedCommandLetter); // [Expected word letter] if((status = read_parameter(block, &char_counter, &value)) != Status_OK) return status; + if(gc_state.skip_blocks && letter != 'O') + return Status_OK; + if(!is_user_mcode && isnanf(value)) FAIL(Status_BadNumberFormat); // [Expected word value] @@ -3118,6 +3133,10 @@ status_code_t gc_execute_block (char *block) case NonModal_MacroCall: { +#if NGC_EXPRESSIONS_ENABLE + ngc_named_param_set("_value", 0.0f); + ngc_named_param_set("_value_returned", 0.0f); +#endif status_code_t status = grbl.on_macro_execute((macro_id_t)gc_block.values.p); return status == Status_Unhandled ? Status_GcodeValueOutOfRange : status; diff --git a/gcode.h b/gcode.h index 65ca290..34c6ccf 100644 --- a/gcode.h +++ b/gcode.h @@ -530,6 +530,7 @@ typedef struct { bool is_laser_ppi_mode; bool is_rpm_rate_adjusted; bool tool_change; + bool skip_blocks; //!< true if skipping conditional blocks status_code_t last_error; //!< last return value from parser //!< The following variables are not cleared upon warm restart when COMPATIBILITY_LEVEL <= 1 bool g92_coord_offset_applied; //!< true when G92 offset applied diff --git a/grbl.h b/grbl.h index 6a05747..877c9ce 100644 --- a/grbl.h +++ b/grbl.h @@ -42,7 +42,7 @@ #else #define GRBL_VERSION "1.1f" #endif -#define GRBL_BUILD 20230526 +#define GRBL_BUILD 20230529 #define GRBL_URL "https://github.com/grblHAL" diff --git a/ngc_flowctrl.c b/ngc_flowctrl.c new file mode 100644 index 0000000..1e9dbfb --- /dev/null +++ b/ngc_flowctrl.c @@ -0,0 +1,417 @@ +/* + ngc_flowctrl.c - An embedded CNC Controller with rs274/ngc (g-code) support + + Program flow control, for filesystem macros + + Part of grblHAL + + Copyright (c) 2023 Terje Io + + Grbl 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. + + Grbl 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 Grbl. If not, see . +*/ + +#include "hal.h" + +#if NGC_EXPRESSIONS_ENABLE + +#include + +#include "errors.h" +#include "ngc_expr.h" +#include "ngc_params.h" + +typedef enum { + NGCFlowCtrl_NoOp = 0, + NGCFlowCtrl_If, + NGCFlowCtrl_ElseIf, + NGCFlowCtrl_Else, + NGCFlowCtrl_EndIf, + NGCFlowCtrl_Do, + NGCFlowCtrl_Continue, + NGCFlowCtrl_Break, + NGCFlowCtrl_While, + NGCFlowCtrl_EndWhile, + NGCFlowCtrl_Repeat, + NGCFlowCtrl_EndRepeat, + NGCFlowCtrl_Return, + NGCFlowCtrl_RaiseAlarm, + NGCFlowCtrl_RaiseError +} ngc_cmd_t; + +typedef struct { + uint32_t o_label; + ngc_cmd_t operation; + vfs_file_t *file; + size_t file_pos; + char *expr; + uint32_t repeats; + bool skip; + bool handled; +} ngc_cmd_stack_t; + +static ngc_cmd_stack_t stack[10] = {0}; +static int_fast8_t stack_index = -1; + +static status_code_t read_command (char *line, uint_fast8_t *pos, ngc_cmd_t *operation) +{ + char c = line[*pos]; + status_code_t status = Status_OK; + + (*pos)++; + + switch(c) { + + case 'A': + if (!strncmp(line + *pos, "LARM", 4)) { + *operation = NGCFlowCtrl_RaiseAlarm; + *pos += 4; + } else + status = Status_FlowControlSyntaxError; // Unknown operation name starting with G + break; + + case 'B': + if (!strncmp(line + *pos, "REAK", 4)) { + *operation = NGCFlowCtrl_Break; + *pos += 4; + } else + status = Status_FlowControlSyntaxError; // Unknown operation name starting with G + break; + + case 'C': + if (!strncmp(line + *pos, "ONTINUE", 7)) { + *operation = NGCFlowCtrl_Continue; + *pos += 7; + } else + status = Status_FlowControlSyntaxError; // Unknown operation name starting with G + break; + + case 'D': + if (line[*pos] == 'O') { + *operation = NGCFlowCtrl_Do; + (*pos)++; + } else + status = Status_FlowControlSyntaxError; // Unknown operation name starting with R + break; + + case 'E': + if (!strncmp(line + *pos, "LSEIF", 5)) { + *operation = NGCFlowCtrl_ElseIf; + *pos += 5; + } else if (!strncmp(line + *pos, "LSE", 3)) { + *operation = NGCFlowCtrl_Else; + *pos += 3; + } else if (!strncmp(line + *pos, "NDIF", 4)) { + *operation = NGCFlowCtrl_EndIf; + *pos += 4; + } else if (!strncmp(line + *pos, "NDWHILE", 7)) { + *operation = NGCFlowCtrl_EndWhile; + *pos += 7; + } else if (!strncmp(line + *pos, "NDREPEAT", 8)) { + *operation = NGCFlowCtrl_EndRepeat; + *pos += 8; + } else if (!strncmp(line + *pos, "RROR", 4)) { + *operation = NGCFlowCtrl_RaiseError; + *pos += 4; + } else + status = Status_FlowControlSyntaxError; // Unknown operation name starting with G + break; + + case 'I': + if(line[*pos] == 'F') { + *operation = NGCFlowCtrl_If; + (*pos)++; + } else + *operation = Status_FlowControlSyntaxError; + break; + + case 'R': + if (!strncmp(line + *pos, "EPEAT", 5)) { + *operation = NGCFlowCtrl_Repeat; + *pos += 5; + } else if (!strncmp(line + *pos, "ETURN", 5)) { + *operation = NGCFlowCtrl_Return; + *pos += 5; + } else + *operation = Status_FlowControlSyntaxError; + break; + + case 'W': + if (!strncmp(line + *pos, "HILE", 4)) { + *operation = NGCFlowCtrl_While; + *pos += 4; + } else + status = Status_FlowControlSyntaxError; // Unknown operation name starting with G + break; + + default: + status = Status_FlowControlSyntaxError; // Unknown operation name + } + + return status; +} + +void ngc_flowctrl_init (void) +{ + if(stack_index >= 0) do { + if(stack[stack_index].expr) + free(stack[stack_index].expr); + } while(--stack_index != -1); + + memset(stack, 0, sizeof(stack)); +} + +status_code_t ngc_flowctrl (uint32_t o_label, char *line, uint_fast8_t *pos, bool *skip) +{ + float value; + bool skipping; + ngc_cmd_t operation, last_op; + + status_code_t status; + + if((status = read_command(line, pos, &operation)) != Status_OK) + return status; + + skipping = stack_index >= 0 && stack[stack_index].skip; + last_op = stack_index >= 0 ? stack[stack_index].operation : NGCFlowCtrl_NoOp; + + switch(operation) { + + case NGCFlowCtrl_If: + if(!skipping && (status = ngc_eval_expression(line, pos, &value)) == Status_OK) { + stack[++stack_index].o_label = o_label; + stack[stack_index].operation = operation; + stack[stack_index].o_label = o_label; + stack[stack_index].file = sys.macro_file; + stack[stack_index].skip = value == 0.0f; + stack[stack_index].handled = !stack[stack_index].skip; + } + break; + + case NGCFlowCtrl_ElseIf: + if(last_op == NGCFlowCtrl_If || last_op == NGCFlowCtrl_ElseIf) { + if(o_label == stack[stack_index].o_label && + !(stack[stack_index].skip = stack[stack_index].handled) && !stack[stack_index].handled && + (status = ngc_eval_expression(line, pos, &value)) == Status_OK) { + if(!(stack[stack_index].skip = value == 0.0f)) { + stack[stack_index].operation = operation; + stack[stack_index].handled = true; + } + } + } else + status = Status_FlowControlSyntaxError; + break; + + case NGCFlowCtrl_Else: + if(last_op == NGCFlowCtrl_If || last_op == NGCFlowCtrl_ElseIf) { + if(o_label == stack[stack_index].o_label) { + if(!(stack[stack_index].skip = stack[stack_index].handled)) + stack[stack_index].operation = operation; + } + } else + status = Status_FlowControlSyntaxError; + break; + + case NGCFlowCtrl_EndIf: + if(last_op == NGCFlowCtrl_If || last_op == NGCFlowCtrl_ElseIf || last_op == NGCFlowCtrl_Else) { + if(o_label == stack[stack_index].o_label) + stack_index--; + } else + status = Status_FlowControlSyntaxError; + break; + + case NGCFlowCtrl_Do: + if(sys.macro_file) { + if(!skipping) { + stack[++stack_index].operation = operation; + stack[stack_index].o_label = o_label; + stack[stack_index].file = sys.macro_file; + stack[stack_index].file_pos = vfs_tell(sys.macro_file); + stack[stack_index].skip = false; + } + } else + status = Status_FlowControlNotExecutingMacro; + break; + + case NGCFlowCtrl_While: + if(sys.macro_file) { + char *expr = line + *pos; + if(!skipping && (status = ngc_eval_expression(line, pos, &value)) == Status_OK) { + if(last_op == NGCFlowCtrl_Do) { + if(o_label == stack[stack_index].o_label) { + if(value != 0.0f) + vfs_seek(sys.macro_file, stack[stack_index].file_pos); + else + stack_index--; + } + } else { + stack[++stack_index].operation = operation; + stack[stack_index].o_label = o_label; + if(!(stack[stack_index].skip = value == 0.0f)) { + if((stack[stack_index].expr = malloc(strlen(expr) + 1))) { + strcpy(stack[stack_index].expr, expr); + stack[stack_index].file = sys.macro_file; + stack[stack_index].file_pos = vfs_tell(sys.macro_file); + } else + status = Status_FlowControlOutOfMemory; + } + } + } + } else + status = Status_FlowControlNotExecutingMacro; + break; + + case NGCFlowCtrl_EndWhile: + if(sys.macro_file) { + if(last_op == NGCFlowCtrl_While) { + if(o_label == stack[stack_index].o_label) { + uint_fast8_t pos = 0; + if(!stack[stack_index].skip && (status = ngc_eval_expression(stack[stack_index].expr, &pos, &value)) == Status_OK) { + if(!(stack[stack_index].skip = value == 0)) + vfs_seek(sys.macro_file, stack[stack_index].file_pos); + } + if(stack[stack_index].skip) { + if(stack[stack_index].expr) { + free(stack[stack_index].expr); + stack[stack_index].expr = NULL; + } + stack_index--; + } + } + } else + status = Status_FlowControlSyntaxError; + } else + status = Status_FlowControlNotExecutingMacro; + break; + + case NGCFlowCtrl_Repeat: + if(sys.macro_file) { + if(!skipping && (status = ngc_eval_expression(line, pos, &value)) == Status_OK) { + stack[++stack_index].operation = operation; + stack[stack_index].o_label = o_label; + if(!(stack[stack_index].skip = value == 0.0f)) { + stack[stack_index].file = sys.macro_file; + stack[stack_index].file_pos = vfs_tell(sys.macro_file); + stack[stack_index].repeats = (uint32_t)value; + } + } + } else + status = Status_FlowControlNotExecutingMacro; + break; + + case NGCFlowCtrl_EndRepeat: + if(sys.macro_file) { + if(last_op == NGCFlowCtrl_Repeat) { + if(o_label == stack[stack_index].o_label) { + if(stack[stack_index].repeats && --stack[stack_index].repeats) + vfs_seek(sys.macro_file, stack[stack_index].file_pos); + else + stack_index--; + } + } else + status = Status_FlowControlSyntaxError; + } else + status = Status_FlowControlNotExecutingMacro; + break; + + case NGCFlowCtrl_Break: + if(sys.macro_file) { + if(last_op == NGCFlowCtrl_Do || last_op == NGCFlowCtrl_While || last_op == NGCFlowCtrl_Repeat) { + if(o_label == stack[stack_index].o_label) { + stack[stack_index].repeats = 0; + stack[stack_index].skip = true; + } + } else + status = Status_FlowControlSyntaxError; + } else + status = Status_FlowControlNotExecutingMacro; + break; + + case NGCFlowCtrl_Continue: + if(sys.macro_file) { + if(o_label == stack[stack_index].o_label) switch(last_op) { + + case NGCFlowCtrl_Repeat: + if(stack[stack_index].repeats && --stack[stack_index].repeats) + vfs_seek(sys.macro_file, stack[stack_index].file_pos); + else + stack_index--; + break; + + case NGCFlowCtrl_Do: + vfs_seek(sys.macro_file, stack[stack_index].file_pos); + break; + + case NGCFlowCtrl_While: + { + uint_fast8_t pos = 0; + if(!stack[stack_index].skip && (status = ngc_eval_expression(stack[stack_index].expr, &pos, &value)) == Status_OK) { + if(!(stack[stack_index].skip = value == 0)) + vfs_seek(sys.macro_file, stack[stack_index].file_pos); + } + if(stack[stack_index].skip) { + if(stack[stack_index].expr) { + free(stack[stack_index].expr); + stack[stack_index].expr = NULL; + } + stack_index--; + } + } + break; + + default: + status = Status_FlowControlSyntaxError; + break; + } else + status = Status_FlowControlSyntaxError; + } else + status = Status_FlowControlNotExecutingMacro; + break; + + case NGCFlowCtrl_RaiseAlarm: + if(ngc_eval_expression(line, pos, &value) == Status_OK) + system_raise_alarm((alarm_code_t)value); + break; + + case NGCFlowCtrl_RaiseError: + if(ngc_eval_expression(line, pos, &value) == Status_OK) + status = (status_code_t)value; + break; + + case NGCFlowCtrl_Return: + if(!skipping && grbl.on_macro_return) { + stack_index = -1; + if(ngc_eval_expression(line, pos, &value) == Status_OK) { + ngc_named_param_set("_value", value); + ngc_named_param_set("_value_returned", 1.0f); + } else + ngc_named_param_set("_value_returned", 0.0f); + grbl.on_macro_return(); + } else + status = Status_FlowControlNotExecutingMacro; + break; + + default: + status = Status_FlowControlSyntaxError; + } + + if(status != Status_OK) { + ngc_flowctrl_init(); + *skip = false; + } else + *skip = stack_index >= 0 && stack[stack_index].skip; + + return status; +} + +#endif // NGC_EXPRESSIONS_ENABLE diff --git a/ngc_flowctrl.h b/ngc_flowctrl.h new file mode 100644 index 0000000..2544981 --- /dev/null +++ b/ngc_flowctrl.h @@ -0,0 +1,30 @@ +/* + ngc_flowctrl.h - An embedded CNC Controller with rs274/ngc (g-code) support + + Program flow control, for filesystem macros + + Part of grblHAL + + Copyright (c) 2023 Terje Io + + Grbl 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. + + Grbl 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 Grbl. If not, see . +*/ + +#ifndef _NGC_FLOWCTRL_H_ +#define _NGC_FLOWCTRL_H_ + +void ngc_flowctrl_init (void); +status_code_t ngc_flowctrl (uint32_t o_label, char *line, uint_fast8_t *pos, bool *skip); + +#endif diff --git a/system.h b/system.h index 3406148..5ba06dc 100644 --- a/system.h +++ b/system.h @@ -27,6 +27,9 @@ #include "probe.h" #include "alarms.h" #include "messages.h" +#if NGC_EXPRESSIONS_ENABLE +#include "vfs.h" +#endif /*! @name System executor bit map. \anchor rt_exec @@ -282,7 +285,10 @@ typedef struct system { volatile probing_state_t probing_state; //!< Probing state value. Used to coordinate the probing cycle with stepper ISR. volatile rt_exec_t rt_exec_state; //!< Realtime executor bitflag variable for state management. See EXEC bitmasks. volatile uint_fast16_t rt_exec_alarm; //!< Realtime executor bitflag variable for setting various alarms. - int32_t var5399; //!< Last result from M66 - wait on input + int32_t var5399; //!< Last result from M66 - wait on input. +#if NGC_EXPRESSIONS_ENABLE + vfs_file_t *macro_file; //!< File handle of current G65 macro executing. +#endif #ifdef PID_LOG pid_data_t pid_log; #endif