diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f2e121..26dc6f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,9 @@ target_sources(grbl INTERFACE ${CMAKE_CURRENT_LIST_DIR}/tool_change.c ${CMAKE_CURRENT_LIST_DIR}/alarms.c ${CMAKE_CURRENT_LIST_DIR}/errors.c + ${CMAKE_CURRENT_LIST_DIR}/ngc_params.c + ${CMAKE_CURRENT_LIST_DIR}/ngc_expr.c + ${CMAKE_CURRENT_LIST_DIR}/regex.c ) target_include_directories(grbl INTERFACE ${CMAKE_CURRENT_LIST_DIR}) diff --git a/README.md b/README.md index 016b008..348fae4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ It has been written to complement grblHAL and has features such as proper keyboa --- -Latest build date is 20210907, see the [changelog](changelog.md) for details. +Latest build date is 20210928, see the [changelog](changelog.md) for details. __NOTE:__ Drivers built with more than three axes configured \(`N_AXIS` > `3`\) will force a settings reset when upgraded. Backup and restore of settings is recommended for these. --- @@ -68,7 +68,7 @@ List of Supported G-Codes: - Coolant Control: M7, M8, M9 - Spindle Control: M3, M4, M5 - Tool Change: M6* (Two modes possible: manual** - supports jogging, ATC), M61 - - Switches: M49, M50, M51, M53 + - Switches: M48, M49, M50, M51, M53 - Output control***: M62, M63, M64, M65, M66, M67, M68 - Valid Non-Command Words: A*, B*, C*, F, H*, I, J, K, L, N, P, Q*, R, S, T, X, Y, Z @@ -80,4 +80,4 @@ List of Supported G-Codes: Some [plugins](https://github.com/grblHAL/plugins) implements additional M-codes. --- -2021-09-08 +2021-09-28 diff --git a/changelog.md b/changelog.md index 1f8ece3..ca43149 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,26 @@ ## grblHAL changelog +Build 20210928: + +Core: +* Changed safety door/parking handling to be compliant with legacy Grbl - now a cycle start command has to be issued to resume after the door is closed. +* Added `$384` setting for controlling G92 offset persistence, set to `1` to disable persistence across a reboot, `0` to enable. Only available if [compatibility level](https://github.com/grblHAL/core/wiki/Compatibility-level) is < 2, default value is `0`. +* Improved `$help` command output and handling, added description to `$$=` output. +* Moved the optional tool table in non-volatile storage \(typically EEPROM\) to above the core area. This allows a larger number of tools \(max. 16\) to be defined. +__NOTE:__ If you have tool table support enabled before upgrading the current table will be lost and possibly also all other settings. Backup and restore! +* Added gcode parameter support. All [NIST RS274NGC version 3](https://www.nist.gov/publications/nist-rs274ngc-interpreter-version-3) parameters (see section 3.2.1) and most [LinuxCNC](http://www.linuxcnc.org/docs/html/gcode/overview.html#_parameters) parameters are supported. + The `$#=` or `$#=` commands can be used to output a parameter value. Replace `` with a parameter number, `` with a parameter name. +__NOTE 1:__ Named parameters and parameters in the range 1 to 5160 are volatile and will not persist across a reboot. +__NOTE 2:__ Space for the volatile parameters is allocated at run-time, available memory \(heap\) sets a limit to how many can be set. +__NOTE 3:__ Maximum name length is 20 characters, maximum number of parameters that can be set in a block \(line\) is 10. +* Added gcode [expression](http://www.linuxcnc.org/docs/html/gcode/overview.html#gcode:expressions) support. This has to be enabled in [grbl/config.h](./config.h) by uncommenting `//#define NGC_EXPRESSIONS_ENABLE 1`. _Experimental_. +__NOTE:__ Processors with limited memory may not compile with this enabled. + +Drivers & plugins: + +* Added [WebUI plugin](https://github.com/grblHAL/Plugin_WebUI) support for some networking capable boards. +* Updated some drivers for internal API changes. Some minor bug fixes. + Build 20210907: Core: diff --git a/config.h b/config.h index c1691c1..760ceef 100644 --- a/config.h +++ b/config.h @@ -83,7 +83,7 @@ __NOTE:__ if switching to a level > 1 please reset non-volatile storage with \a // immediately forces a feed hold and then safely de-energizes the machine. Resuming is blocked until // the safety door is re-engaged. When it is, Grbl will re-energize the machine and then resume on the // previous tool path, as if nothing happened. -// #define ENABLE_SAFETY_DOOR_INPUT_PIN // Default disabled. Uncomment to enable. +//#define ENABLE_SAFETY_DOOR_INPUT_PIN // Default disabled. Uncomment to enable. // After the safety door switch has been toggled and restored, this setting sets the power-up delay // between restoring the spindle and coolant and resuming the cycle. @@ -472,9 +472,24 @@ __NOTE:__ these definitions are only referenced in this file. Do __NOT__ change! //#define DEFAULT_REPORT_PARSER_STATE //#define DEFAULT_REPORT_ALARM_SUBSTATE +// G92 offsets is by default stored to non-volatile storage (NVS) on changes and restored on startup +// if COMPATIBILITY_LEVEL is <= 1. If COMPATIBILITY_LEVEL is <= 1 then setting $384 can be used to change this at run-time. +// To allow store/restore of the G92 offset when COMPATIBILITY_LEVEL > 1 uncomment the line below and reset settings with $RST=*. +//#define DISABLE_G92_PERSISTENCE 0 + #if COMPATIBILITY_LEVEL == 0 -// Number of tools in ATC tool table, comment out to disable -// #define N_TOOLS 8 +// Number of tools in tool table, uncomment and edit if neccesary to enable (max. 16 allowed) +//#define N_TOOLS 8 +#endif + +// Sanity checks - N_TOOLS may have been defined on the compiler command line. +#if defined(N_TOOLS) && N_TOOLS == 0 +#undef N_TOOLS +#endif + +#if defined(N_TOOLS) && N_TOOLS > 16 +#undef N_TOOLS +#define N_TOOLS 16 #endif // Max number of entries in log for PID data reporting, to be used for tuning @@ -624,4 +639,7 @@ __NOTE:__ these definitions are only referenced in this file. Do __NOT__ change! #endif // DEFAULT_HOMING_ENABLE +// Uncomment to enable experimental support for parameters and expressions +//#define NGC_EXPRESSIONS_ENABLE 1 + #endif diff --git a/defaults.h b/defaults.h index baa0972..8b54fd4 100644 --- a/defaults.h +++ b/defaults.h @@ -37,6 +37,14 @@ // Note: DEFAULT_ACCELERATION is only referenced in this file #define DEFAULT_ACCELERATION (10.0f * 60.0f * 60.0f) // 10*60*60 mm/min^2 = 10 mm/sec^2 +#ifndef DISABLE_G92_PERSISTENCE +#if COMPATIBILITY_LEVEL <= 1 +#define DISABLE_G92_PERSISTENCE 0 +#else +#define DISABLE_G92_PERSISTENCE 1 +#endif +#endif + #ifdef DEFAULT_REPORT_MACHINE_POSITION #undef DEFAULT_REPORT_MACHINE_POSITION #define DEFAULT_REPORT_MACHINE_POSITION 1 @@ -609,6 +617,12 @@ #define INVERT_COOLANT_MIST_PIN 0 #endif +#ifndef NGC_EXPRESSIONS_ENABLE +#define NGC_EXPRESSIONS_ENABLE 0 +#else +#define NGC_N_ASSIGN_PARAMETERS_PER_BLOCK 10 +#endif + // --------------------------------------------------------------------------------------- // COMPILE-TIME ERROR CHECKING OF DEFINE VALUES: diff --git a/driver_opts.h b/driver_opts.h index df72ffb..bb30659 100644 --- a/driver_opts.h +++ b/driver_opts.h @@ -240,7 +240,7 @@ #if NETWORK_IPMODE < 0 || NETWORK_IPMODE > 2 #error "Invalid IP mode selected!" #endif -#if NETWORK_WEBSOCKET_PORT == NETWORK_HTTP_PORT +#if HTTP_ENABLE && NETWORK_WEBSOCKET_PORT == NETWORK_HTTP_PORT #warning "HTTP and WebSocket protocols cannot share the same port!" #endif #endif diff --git a/errors.c b/errors.c index 32b2b33..baa6025 100644 --- a/errors.c +++ b/errors.c @@ -81,7 +81,15 @@ PROGMEM static const status_detail_t status_detail[] = { { Status_MotorFault, "Motor fault", "Motor fault." }, { Status_SettingValueOutOfRange, "Value out of range.", "Setting value is out of range." }, { Status_SettingDisabled, "Setting disabled", "Setting is not available, possibly due to limited driver support." }, - { Status_GcodeInvalidRetractPosition, "Invalid gcode ID:54", "Retract position is less than drill depth." } + { Status_GcodeInvalidRetractPosition, "Invalid gcode ID:54", "Retract position is less than drill depth." }, +#if NGC_EXPRESSIONS_ENABLE + { Status_ExpressionUknownOp, "Unknown operation found in expression", "Unknown operation found in expression." }, + { Status_ExpressionDivideByZero, "Divide by zero in expression", "Divide by zero in expression attempted." }, + { Status_ExpressionArgumentOutOfRange, "Expression argument out of range", "Too large or too small argrument provided." }, + { Status_ExpressionInvalidArgument, "Invalid expression argument", "Argument is not valid for the operation" }, + { Status_ExpressionSyntaxError, "Syntax error in expression", "Expression is not valid." }, + { Status_ExpressionInvalidResult, "Invalid result returned from expression", "Either NAN (not a number) or infinity was returned from expression." } +#endif }; static error_details_t details = { diff --git a/errors.h b/errors.h index 79b6543..c7d6ef5 100644 --- a/errors.h +++ b/errors.h @@ -93,6 +93,15 @@ typedef enum { Status_SDFileEmpty = 64, Status_BTInitError = 70, + +// + Status_ExpressionUknownOp = 71, + Status_ExpressionDivideByZero = 72, + Status_ExpressionArgumentOutOfRange = 73, + Status_ExpressionInvalidArgument = 74, + Status_ExpressionSyntaxError = 75, + Status_ExpressionInvalidResult = 76, + Status_Unhandled, // For internal use only Status_StatusMax = Status_Unhandled } status_code_t; diff --git a/gcode.c b/gcode.c index c6a4ee3..5ab0d4a 100644 --- a/gcode.c +++ b/gcode.c @@ -30,6 +30,11 @@ #include "protocol.h" #include "state_machine.h" +#if NGC_EXPRESSIONS_ENABLE +#include "ngc_expr.h" +#include "ngc_params.h" +#endif + // NOTE: Max line number is defined by the g-code standard to be 99999. It seems to be an // arbitrary value, and some GUIs may require more. So we increased it based on a max safe // value when converting a float (7.2 digit precision)s to an integer. @@ -307,10 +312,8 @@ void gc_init (void) if (!settings_read_coord_data(gc_state.modal.coord_system.id, &gc_state.modal.coord_system.xyz)) grbl.report.status_message(Status_SettingReadFail); -#if COMPATIBILITY_LEVEL <= 1 - if (sys.cold_start && !settings_read_coord_data(CoordinateSystem_G92, &gc_state.g92_coord_offset)) + if (sys.cold_start && !settings.flags.g92_is_volatile && !settings_read_coord_data(CoordinateSystem_G92, &gc_state.g92_coord_offset)) grbl.report.status_message(Status_SettingReadFail); -#endif // if(settings.flags.lathe_mode) // gc_state.modal.plane_select = PlaneSelect_ZX; @@ -373,12 +376,126 @@ static status_code_t init_sync_motion (plan_line_data_t *pl_data, float pitch) return Status_OK; } -// Executes one block (line) of 0-terminated G-Code. The block is assumed to contain only uppercase -// characters and signed floating point values (no whitespace). Comments and block delete -// characters have been removed. In this function, all units and positions are converted and -// exported to grbl's internal functions in terms of (mm, mm/min) and absolute machine -// coordinates, respectively. -status_code_t gc_execute_block(char *block, char *message) +// Remove whitespace, control characters, comments and if block delete is active block delete lines +// else the block delete character. Remaining characters are converted to upper case. +// If the driver handles message comments then the first is extracted and returned in a dynamically +// allocated memory block, the caller must free this after the message has been processed. + +char *gc_normalize_block (char *block, char **message) +{ + char c, *s1, *s2, *comment = NULL; + + // Remove leading whitespace & control characters + while(*block && *block <= ' ') + block++; + + if(*block == ';' || (*block == '/' && sys.flags.block_delete_enabled)) { + *block = '\0'; + return block; + } + + if(*block == '/') + block++; + + s1 = s2 = block; + + while((c = *s1) != '\0') { + + if(c > ' ') switch(c) { + + case ';': + if(!comment) { + *s1 = '\0'; + continue; + } + break; + + case '(': + // TODO: generate error if a left paranthesis is found inside a comment... + comment = s1; + break; + + case ')': + if(comment && !hal.driver_cap.no_gcode_message_handling) { + size_t len = s1 - comment - 4; + if(message && *message == NULL && !strncmp(comment, "(MSG,", 5) && (*message = malloc(len))) { + *s1 = '\0'; + memcpy(*message, comment + 5, len); + } + } + comment = NULL; + break; + + default: + if(comment == NULL) + *s2++ = CAPS(c); + break; + } + + if(comment && s1 - comment < 5) + *s1 = CAPS(c); + + s1++; + } + + *s2 = '\0'; + + return block; +} + +#if NGC_EXPRESSIONS_ENABLE + +#define NGC_N_ASSIGN_PARAMETERS_PER_BLOCK 10 + +static ngc_param_t ngc_params[NGC_N_ASSIGN_PARAMETERS_PER_BLOCK]; + +static status_code_t read_parameter (char *line, uint_fast8_t *char_counter, float *value) +{ + char c = *(line + *char_counter); + status_code_t status = Status_OK; + + if(c == '#') { + + (*char_counter)++; + + if(*(line + *char_counter) == '<') { + + (*char_counter)++; + char *pos = line + *char_counter; + + while(*line && *line != '>') + line++; + + *char_counter += line - pos + 1; + + if(*line == '>') { + *line = '\0'; + if(!ngc_named_param_get(pos, value)) + status = Status_BadNumberFormat; + } else + status = Status_BadNumberFormat; + + } else if (read_float(line, char_counter, value)) { + if(!ngc_param_get((ngc_param_id_t)*value, value)) + status = Status_BadNumberFormat; + } else + status = Status_BadNumberFormat; + + } else if(c == '[') + status = ngc_eval_expression(line, char_counter, value); + else if(!read_float(line, char_counter, value)) + *value = NAN; + + return status; +} + +#endif + +// 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. + +status_code_t gc_execute_block(char *block) { static const parameter_words_t axis_words_mask = { .x = On, @@ -428,6 +545,22 @@ status_code_t gc_execute_block(char *block, char *message) static parser_block_t gc_block; +#if NGC_EXPRESSIONS_ENABLE + uint_fast8_t ngc_param_count = 0; +#endif + + char *message = NULL; + + block = gc_normalize_block(block, &message); + + if(block[0] == '\0') { + if(message) { + report_message(message, Message_Plain); + free(message); + } + return Status_OK; + } + // Determine if the line is a program start/end marker. // Old comment from protocol.c: // NOTE: This maybe installed to tell Grbl when a program is running vs manual input, @@ -436,6 +569,10 @@ status_code_t gc_execute_block(char *block, char *message) // functions that empty the planner buffer to execute its task on-time. if (block[0] == CMD_PROGRAM_DEMARCATION && block[1] == '\0') { gc_state.file_run = !gc_state.file_run; + if(message) { + report_message(message, Message_Plain); + free(message); + } return Status_OK; } @@ -490,6 +627,64 @@ status_code_t gc_execute_block(char *block, char *message) while ((letter = block[char_counter++]) != '\0') { // Loop until no more g-code words in block. // Import the next g-code word, expecting a letter followed by a value. Otherwise, error out. + +#if NGC_EXPRESSIONS_ENABLE + + status_code_t status; + + if(letter == '#') { + + if(block[char_counter] == '<') { + + char *s = &block[++char_counter]; + + while(*s && *s != '>') + s++; + + if(*s && *(s + 1) == '=') { + char *name = &block[char_counter]; + *s++ = '\0'; + s++; + char_counter += s - name; + if((status = read_parameter(block, &char_counter, &value)) != Status_OK) + FAIL(status); // [Expected parameter value] + if(!ngc_named_param_set(name, value)) + FAIL(Status_BadNumberFormat); // [Expected equal sign] + } + + } else { + + float param; + if (!read_float(block, &char_counter, ¶m)) + FAIL(Status_BadNumberFormat); // [Expected parameter number] + + if (block[char_counter++] != '=') + FAIL(Status_BadNumberFormat); // [Expected equal sign] + + if((status = read_parameter(block, &char_counter, &value)) != Status_OK) + FAIL(status); // [Expected parameter value] + + if(ngc_param_count < NGC_N_ASSIGN_PARAMETERS_PER_BLOCK && ngc_param_is_rw((ngc_param_id_t)param)) { + ngc_params[ngc_param_count].id = (ngc_param_id_t)param; + ngc_params[ngc_param_count++].value = value; + } else + FAIL(Status_BadNumberFormat); // [Expected parameter value] + } + + continue; + } + + if((letter < 'A') || (letter > 'Z')) + FAIL(Status_ExpectedCommandLetter); // [Expected word letter] + + if((status = read_parameter(block, &char_counter, &value)) != Status_OK) + return status; + + if(!is_user_mcode && isnanf(value)) + FAIL(Status_BadNumberFormat); // [Expected word value] + +#else + if((letter < 'A') || (letter > 'Z')) FAIL(Status_ExpectedCommandLetter); // [Expected word letter] @@ -500,6 +695,8 @@ status_code_t gc_execute_block(char *block, char *message) FAIL(Status_BadNumberFormat); // [Expected word value] } +#endif + // Convert values to smaller uint8 significand and mantissa values for parsing this word. // NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more // accurate than the NIST gcode requirement of x10 when used for commands, but not quite @@ -805,7 +1002,7 @@ status_code_t gc_execute_block(char *block, char *message) if(!settings.parking.flags.enable_override_control) // TODO: check if enabled? FAIL(Status_GcodeUnsupportedCommand); // [Unsupported M command] // no break; - case 49: case 50: case 51: case 53: + case 48: case 49: case 50: case 51: case 53: word_bit.modal_group.M9 = On; gc_block.override_command = (override_mode_t)int_value; break; @@ -1309,9 +1506,14 @@ status_code_t gc_execute_block(char *block, char *message) } switch(gc_block.override_command) { - case Override_FeedSpeed: - gc_block.modal.override_ctrl.feed_rate_disable = gc_block.values.p == 0.0f; - gc_block.modal.override_ctrl.spindle_rpm_disable = gc_block.values.p == 0.0f; + case Override_FeedSpeedEnable: + gc_block.modal.override_ctrl.feed_rate_disable = Off; + gc_block.modal.override_ctrl.spindle_rpm_disable = Off; + break; + + case Override_FeedSpeedDisable: + gc_block.modal.override_ctrl.feed_rate_disable = On; + gc_block.modal.override_ctrl.spindle_rpm_disable = On; break; case Override_FeedRate: @@ -2571,25 +2773,29 @@ status_code_t gc_execute_block(char *block, char *message) break; case NonModal_SetCoordinateOffset: // G92 + gc_state.g92_coord_offset_applied = true; // TODO: check for all zero? memcpy(gc_state.g92_coord_offset, gc_block.values.xyz, sizeof(gc_state.g92_coord_offset)); -#if COMPATIBILITY_LEVEL <= 1 - settings_write_coord_data(CoordinateSystem_G92, &gc_state.g92_coord_offset); // Save G92 offsets to non-volatile storage -#endif + if(!settings.flags.g92_is_volatile) + settings_write_coord_data(CoordinateSystem_G92, &gc_state.g92_coord_offset); // Save G92 offsets to non-volatile storage system_flag_wco_change(); break; case NonModal_ResetCoordinateOffset: // G92.1 + gc_state.g92_coord_offset_applied = false; clear_vector(gc_state.g92_coord_offset); // Disable G92 offsets by zeroing offset vector. - settings_write_coord_data(CoordinateSystem_G92, &gc_state.g92_coord_offset); // Save G92 offsets to non-volatile storage + if(!settings.flags.g92_is_volatile) + settings_write_coord_data(CoordinateSystem_G92, &gc_state.g92_coord_offset); // Save G92 offsets to non-volatile storage system_flag_wco_change(); break; case NonModal_ClearCoordinateOffset: // G92.2 + gc_state.g92_coord_offset_applied = false; clear_vector(gc_state.g92_coord_offset); // Disable G92 offsets by zeroing offset vector. system_flag_wco_change(); break; case NonModal_RestoreCoordinateOffset: // G92.3 + gc_state.g92_coord_offset_applied = true; // TODO: check for all zero? settings_read_coord_data(CoordinateSystem_G92, &gc_state.g92_coord_offset); // Restore G92 offsets from non-volatile storage system_flag_wco_change(); break; @@ -2804,6 +3010,13 @@ status_code_t gc_execute_block(char *block, char *message) gc_state.modal.program_flow = ProgramFlow_Running; // Reset program flow. } +#if NGC_EXPRESSIONS_ENABLE + if(ngc_param_count) do { + ngc_param_count--; + ngc_param_set(ngc_params[ngc_param_count].id, ngc_params[ngc_param_count].value); + } while(ngc_param_count); +#endif + // TODO: % to denote start of program. return Status_OK; diff --git a/gcode.h b/gcode.h index a123459..f590cec 100644 --- a/gcode.h +++ b/gcode.h @@ -167,8 +167,8 @@ typedef enum { Do not alter values! */ typedef enum { - SpindleSpeedMode_RPM = 0, //!< 0 - G96 - Default, must be zero - SpindleSpeedMode_CSS = 1 //!< 1 - G97 + SpindleSpeedMode_RPM = 0, //!< 0 - G97 - Default, must be zero + SpindleSpeedMode_CSS = 1 //!< 1 - G96 } spindle_rpm_mode_t; /*! Modal Group M4: Program flow @@ -186,7 +186,8 @@ typedef enum { // Modal Group M9: Override control typedef enum { - Override_FeedSpeed = 49, //!< 49 - M49 + Override_FeedSpeedEnable = 48, //!< 48 - M48 + Override_FeedSpeedDisable = 49, //!< 49 - M49 Override_FeedRate = 50, //!< 50 - M50 Override_SpindleSpeed = 51, //!< 51 - M51 Override_FeedHold = 53, //!< 53 - M53 @@ -397,7 +398,7 @@ typedef union { // NOTE: When this struct is zeroed, the above defines set the defaults for the system. typedef struct { motion_mode_t motion; //!< {G0,G1,G2,G3,G38.2,G80} - feed_mode_t feed_mode; //!< {G93,G94} + feed_mode_t feed_mode; //!< {G93,G94,G95} bool units_imperial; //!< {G20,G21} bool distance_incremental; //!< {G90,G91} bool diameter_mode; //!< {G7,G8} Lathe diameter mode. @@ -496,6 +497,7 @@ typedef struct { bool tool_change; 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 float g92_coord_offset[N_AXIS]; //!< Retains the G92 coordinate offset (work coordinates) relative to //!< machine zero in mm. Persistent and loaded from non-volatile storage //!< on boot when COMPATIBILITY_LEVEL <= 1 @@ -534,8 +536,10 @@ typedef struct { // Initialize the parser void gc_init (void); +char *gc_normalize_block (char *block, char **message); + // Execute one block of rs275/ngc/g-code -status_code_t gc_execute_block (char *block, char *message); +status_code_t gc_execute_block (char *block); // Sets g-code parser position in mm. Input in steps. Called by the system abort and hard // limit pull-off routines. diff --git a/grbl.h b/grbl.h index be581b7..04a8580 100644 --- a/grbl.h +++ b/grbl.h @@ -34,7 +34,7 @@ #else #define GRBL_VERSION "1.1f" #endif -#define GRBL_VERSION_BUILD "20210907" +#define GRBL_VERSION_BUILD "20210928" // The following symbols are set here if not already set by the compiler or in config.h // Do NOT change here! diff --git a/grbllib.c b/grbllib.c index 565acb6..6aa720f 100644 --- a/grbllib.c +++ b/grbllib.c @@ -54,7 +54,6 @@ struct system sys = {0}; //!< System global variable structure. grbl_t grbl; grbl_hal_t hal; - #ifdef KINEMATICS_API kinematics_t kinematics; @@ -90,11 +89,6 @@ void dummy_bool_handler (bool arg) int grbl_enter (void) { -#ifdef N_TOOLS - assert(NVS_ADDR_GLOBAL + sizeof(settings_t) + NVS_CRC_BYTES < NVS_ADDR_TOOL_TABLE); -#else - assert(NVS_ADDR_GLOBAL + sizeof(settings_t) + NVS_CRC_BYTES < NVS_ADDR_PARAMETERS); -#endif assert(NVS_ADDR_PARAMETERS + N_CoordinateSystems * (sizeof(coord_data_t) + NVS_CRC_BYTES) < NVS_ADDR_STARTUP_BLOCK); assert(NVS_ADDR_STARTUP_BLOCK + N_STARTUP_LINE * (sizeof(stored_line_t) + NVS_CRC_BYTES) < NVS_ADDR_BUILD_INFO); @@ -106,6 +100,7 @@ int grbl_enter (void) grbl.enqueue_gcode = protocol_enqueue_gcode; grbl.enqueue_realtime_command = stream_enqueue_realtime_command; grbl.on_report_options = dummy_bool_handler; + grbl.on_report_command_help = system_command_help; // Clear all and set some HAL function pointers memset(&hal, 0, sizeof(grbl_hal_t)); diff --git a/ngc_expr.c b/ngc_expr.c new file mode 100644 index 0000000..2f69728 --- /dev/null +++ b/ngc_expr.c @@ -0,0 +1,796 @@ +// ngc_expr.c - derived from: + +/******************************************************************** +* Description: interp_execute.cc +* +* Derived from a work by Thomas Kramer +* +* Author: +* License: GPL Version 2 +* System: Linux +* +* Copyright (c) 2004 All rights reserved. +* +* Last change: +********************************************************************/ + +/* Modified by Terje Io for grblHAL */ + + +#include "nuts_bolts.h" + +#if NGC_EXPRESSIONS_ENABLE + +#include +#include +#include +#include +#include + +#include "errors.h" +#include "ngc_expr.h" +#include "ngc_params.h" + +#define MAX_STACK 7 + +typedef enum { + NGCBinaryOp_NoOp = 0, + NGCBinaryOp_DividedBy, + NGCBinaryOp_Modulo, + NGCBinaryOp_Power, + NGCBinaryOp_Times, + NGCBinaryOp_Binary2 = NGCBinaryOp_Times, + NGCBinaryOp_And2, + NGCBinaryOp_ExclusiveOR, + NGCBinaryOp_Minus, + NGCBinaryOp_NotExclusiveOR, + NGCBinaryOp_Plus, + NGCBinaryOp_RightBracket, + NGCBinaryOp_RelationalFirst, + NGCBinaryOp_LT = NGCBinaryOp_RelationalFirst, + NGCBinaryOp_EQ, + NGCBinaryOp_NE, + NGCBinaryOp_LE, + NGCBinaryOp_GE, + NGCBinaryOp_GT, + NGCBinaryOp_RelationalLast = NGCBinaryOp_GT, +} ngc_binary_op_t; + +typedef enum { + NGCUnaryOp_ABS = 1, + NGCUnaryOp_ACOS, + NGCUnaryOp_ASIN, + NGCUnaryOp_ATAN, + NGCUnaryOp_COS, + NGCUnaryOp_EXP, + NGCUnaryOp_FIX, + NGCUnaryOp_FUP, + NGCUnaryOp_LN, + NGCUnaryOp_Round, + NGCUnaryOp_SIN, + NGCUnaryOp_SQRT, + NGCUnaryOp_TAN, + NGCUnaryOp_Exists, // Not implemented +} ngc_unary_op_t; + +/*! \brief Executes the operations: /, MOD, ** (POW), *. + +\param lhs pointer to the left hand side operand and result. +\param operation \ref ngc_binary_op_t enum value. +\param rhs pointer to the right hand side operand. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t execute_binary1 (float *lhs, ngc_binary_op_t operation, float *rhs) +{ + status_code_t status = Status_OK; + + switch (operation) { + + case NGCBinaryOp_DividedBy: + if(*rhs == 0.0f || *rhs == -0.0f) + status = Status_ExpressionDivideByZero; // Attempt to divide by zero + else + *lhs = *lhs / *rhs; + break; + + case NGCBinaryOp_Modulo: // always calculates a positive answer + *lhs = fmodf(*lhs, *rhs); + if(*lhs < 0.0f) + *lhs = *lhs + fabsf(*rhs); + break; + + case NGCBinaryOp_Power: + if(*lhs < 0.0f && floorf(*rhs) != *rhs) + status = Status_ExpressionInvalidArgument; // Attempt to raise negative value to non-integer power + else + *lhs = powf(*lhs, *rhs); + break; + + case NGCBinaryOp_Times: + *lhs = *lhs * *rhs; + break; + + default: + status = Status_ExpressionUknownOp; + } + + return status; +} + +/*! \brief Executes the operations: +, -, AND, OR, XOR, EQ, NE, LT, LE, GT, GE +The RS274/NGC manual does not say what +the calculated value of the logical operations should be. This +function calculates either 1.0 (meaning true) or 0.0 (meaning false). +Any non-zero input value is taken as meaning true, and only 0.0 means false. + +\param lhs pointer to the left hand side operand and result. +\param operation \ref ngc_binary_op_t enum value. +\param rhs pointer to the right hand side operand. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t execute_binary2 (float *lhs, ngc_binary_op_t operation, float *rhs) +{ + switch(operation) { + + case NGCBinaryOp_And2: + *lhs = ((*lhs == 0.0f) || (*rhs == 0.0f)) ? 0.0f : 1.0f; + break; + + case NGCBinaryOp_ExclusiveOR: + *lhs = (((*lhs == 0.0f) && (*rhs != 0.0f)) || ((*lhs != 0.0f) && (*rhs == 0.0f))) ? 1.0f : 0.0f; + break; + + case NGCBinaryOp_Minus: + *lhs = (*lhs - *rhs); + break; + + case NGCBinaryOp_NotExclusiveOR: + *lhs = ((*lhs != 0.0f) || (*rhs != 0.0f)) ? 1.0f : 0.0f; + break; + + case NGCBinaryOp_Plus: + *lhs = (*lhs + *rhs); + break; + + case NGCBinaryOp_LT: + *lhs = (*lhs < *rhs) ? 1.0f : 0.0f; + break; + + case NGCBinaryOp_EQ: + { + float diff = *lhs - *rhs; + diff = (diff < 0.0f) ? -diff : diff; + *lhs = (diff < TOLERANCE_EQUAL) ? 1.0f : 0.0f; + } + break; + + case NGCBinaryOp_NE: + { + float diff = *lhs - *rhs; + diff = (diff < 0.0f) ? -diff : diff; + *lhs = (diff >= TOLERANCE_EQUAL) ? 1.0f : 0.0f; + } + break; + + case NGCBinaryOp_LE: + *lhs = (*lhs <= *rhs) ? 1.0f : 0.0f; + break; + + case NGCBinaryOp_GE: + *lhs = (*lhs >= *rhs) ? 1.0f : 0.0f; + break; + + case NGCBinaryOp_GT: + *lhs = (*lhs > *rhs) ? 1.0f : 0.0f; + break; + + default: + return Status_ExpressionUknownOp; + } + + return Status_OK; +} + +/*! \brief Executes a binary operation. + +This just calls either execute_binary1 or execute_binary2. + +\param lhs pointer to the left hand side operand and result. +\param operation \ref ngc_binary_op_t enum value. +\param rhs pointer to the right hand side operand. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t execute_binary (float *lhs, ngc_binary_op_t operation, float *rhs) +{ + if (operation <= NGCBinaryOp_Binary2) + return execute_binary1(lhs, operation, rhs); + + return execute_binary2(lhs, operation, rhs); +} + +/*! \brief Executes an unary operation: ABS, ACOS, ASIN, COS, EXP, FIX, FUP, LN, ROUND, SIN, SQRT, TAN + +All angle measures in the input or output are in degrees. + +\param operand pointer to the operand. +\param operation \ref ngc_binary_op_t enum value. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t execute_unary (float *operand, ngc_unary_op_t operation) +{ + status_code_t status = Status_OK; + + switch (operation) { + + case NGCUnaryOp_ABS: + if (*operand < 0.0f) + *operand = (-1.0f * *operand); + break; + + case NGCUnaryOp_ACOS: + if(*operand < -1.0f || *operand > 1.0f) + status = Status_ExpressionArgumentOutOfRange; // Argument to ACOS out of range + else + *operand = acosf(*operand) * DEGRAD; + break; + + case NGCUnaryOp_ASIN: + if(*operand < -1.0f || *operand > 1.0f) + status = Status_ExpressionArgumentOutOfRange; // Argument to ASIN out of range + else + *operand = asinf(*operand) * DEGRAD; + break; + + case NGCUnaryOp_COS: + *operand = cosf(*operand * RADDEG); + break; + + case NGCUnaryOp_Exists: + // do nothing here, result for the EXISTS function is set by read_unary() + break; + + case NGCUnaryOp_EXP: + *operand = expf(*operand); + break; + + case NGCUnaryOp_FIX: + *operand = floor(*operand); + break; + + case NGCUnaryOp_FUP: + *operand = ceilf(*operand); + break; + + case NGCUnaryOp_LN: + if(*operand <= 0.0f) + status = Status_ExpressionArgumentOutOfRange; // Argument to LN out of range + else + *operand = logf(*operand); + break; + + case NGCUnaryOp_Round: + *operand = (float)((int)(*operand + ((*operand < 0.0f) ? -0.5f : 0.5f))); + break; + + case NGCUnaryOp_SIN: + *operand = sinf(*operand * RADDEG); + break; + + case NGCUnaryOp_SQRT: + if(*operand < 0.0f) + status = Status_ExpressionArgumentOutOfRange; // Negative argument to SQRT + else + *operand = sqrtf(*operand); + break; + + case NGCUnaryOp_TAN: + *operand = tanf(*operand *RADDEG); + break; + + default: + status = Status_ExpressionUknownOp; + } + + return status; +} + +/*! \brief Returns an integer representing the precedence level of an operator. + +\param operator \ref ngc_binary_op_t enum value. +\returns precedence level. +*/ +static uint_fast8_t precedence (ngc_binary_op_t operator) +{ + switch(operator) + { + case NGCBinaryOp_RightBracket: + return 1; + + case NGCBinaryOp_And2: + case NGCBinaryOp_ExclusiveOR: + case NGCBinaryOp_NotExclusiveOR: + return 2; + + case NGCBinaryOp_LT: + case NGCBinaryOp_EQ: + case NGCBinaryOp_NE: + case NGCBinaryOp_LE: + case NGCBinaryOp_GE: + case NGCBinaryOp_GT: + return 3; + + case NGCBinaryOp_Minus: + case NGCBinaryOp_Plus: + return 4; + + case NGCBinaryOp_NoOp: + case NGCBinaryOp_DividedBy: + case NGCBinaryOp_Modulo: + case NGCBinaryOp_Times: + return 5; + + case NGCBinaryOp_Power: + return 6; + + default: + break; + } + + return 0; // should never happen +} + +/*! \brief Reads a binary operation out of the line +starting at the index given by the pos offset. If a valid one is found, the +value of operation is set to the symbolic value for that operation. + +\param line pointer to RS274/NGC code (block). +\param pos offset into line where expression starts. +\param operation pointer to \ref ngc_binary_op_t enum value. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t read_operation (char *line, uint_fast8_t *pos, ngc_binary_op_t *operation) +{ + char c = line[*pos]; + status_code_t status = Status_OK; + + (*pos)++; + + switch(c) { + + case '+': + *operation = NGCBinaryOp_Plus; + break; + + case '-': + *operation = NGCBinaryOp_Minus; + break; + + case '/': + *operation = NGCBinaryOp_DividedBy; + break; + + case '*': + if(line[*pos] == '*') { + *operation = NGCBinaryOp_Power; + (*pos)++; + } else + *operation = NGCBinaryOp_Times; + break; + + case ']': + *operation = NGCBinaryOp_RightBracket; + break; + + case 'A': + if (!strncmp(line + *pos, "ND", 2)) { + *operation = NGCBinaryOp_And2; + *pos += 2; + } else + status = Status_ExpressionUknownOp; // Unknown operation name starting with A + break; + + case 'M': + if (!strncmp(line + *pos, "OD", 2)) { + *operation = NGCBinaryOp_Modulo; + *pos += 2; + } else + status = Status_ExpressionUknownOp; // Unknown operation name starting with M + break; + + case 'R': + if (line[*pos] == 'R') { + *operation = NGCBinaryOp_NotExclusiveOR; + (*pos)++; + } else + status = Status_ExpressionUknownOp; // Unknown operation name starting with R + break; + + case 'X': + if (!strncmp(line + *pos, "OR", 2)) { + *operation = NGCBinaryOp_ExclusiveOR; + *pos += 2; + } else + status = Status_ExpressionUknownOp; // Unknown operation name starting with X + break; + + /* relational operators */ + case 'E': + if(line[*pos] == 'Q') { + *operation = NGCBinaryOp_EQ; + (*pos)++; + } else + status = Status_ExpressionUknownOp; // Unknown operation name starting with E + break; + + case 'N': + if(line[*pos] == 'E') { + *operation = NGCBinaryOp_NE; + (*pos)++; + } else + status = Status_ExpressionUknownOp; // Unknown operation name starting with N + break; + + case 'G': + if(line[*pos] == 'E') { + *operation = NGCBinaryOp_GE; + (*pos)++; + } + else if(line[*pos] == 'T') { + *operation = NGCBinaryOp_GT; + (*pos)++; + } else + status = Status_ExpressionUknownOp; // Unknown operation name starting with G + break; + + case 'L': + if(line[*pos] == 'E') { + *operation = NGCBinaryOp_LE; + (*pos)++; + } else if(line[*pos] == 'T') { + *operation = NGCBinaryOp_LT; + (*pos)++; + } + else + status = Status_ExpressionUknownOp;; // Unknown operation name starting with L + break; + +// case '\0': +// status = Status_ExpressionUknownOp; // No operation name found + + default: + status = Status_ExpressionUknownOp; // Unknown operation name + } + + return status; +} + +/*! \brief Reads the name of an unary operation out of the line +starting at the index given by the pos offset. If a valid one is found, the +value of operation is set to the symbolic value for that operation. + +\param line pointer to RS274/NGC code (block). +\param pos offset into line where expression starts. +\param operation pointer to \ref ngc_unary_op_t enum value. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t read_operation_unary (char *line, uint_fast8_t *pos, ngc_unary_op_t *operation) +{ + char c = line[*pos]; + status_code_t status = Status_OK; + + (*pos)++; + + switch(c) { + + case 'A': + if(!strncmp(line + *pos, "BS", 2)) { + *operation = NGCUnaryOp_ABS; + *pos += 2; + } else if(!strncmp(line + *pos, "COS", 3)) { + *operation = NGCUnaryOp_ACOS; + *pos += 3; + } else if(!strncmp(line + *pos, "SIN", 3)) { + *operation = NGCUnaryOp_ASIN; + *pos += 3; + } else if(!strncmp(line + *pos, "TAN", 3)) { + *operation = NGCUnaryOp_ATAN; + *pos += 3; + } else + status = Status_ExpressionUknownOp; + break; + + case 'C': + if(!strncmp(line + *pos, "OS", 2)) { + *operation = NGCUnaryOp_COS; + *pos += 2; + } else + status = Status_ExpressionUknownOp; + break; + + case 'E': + if(!strncmp(line + *pos, "XP", 2)) { + *operation = NGCUnaryOp_EXP; + *pos += 2; +/* } else if(!strncmp(line + *pos, "XISTS", 5)) { + *operation = NGCUnaryOp_Exists; + *pos += 5; */ + } else + status = Status_ExpressionUknownOp; + break; + + case 'F': + if(!strncmp(line + *pos, "IX", 2)) { + *operation = NGCUnaryOp_FIX; + *pos += 2; + } else if(!strncmp(line + *pos, "UP", 2)) { + *operation = NGCUnaryOp_FUP; + *pos += 2; + } else + status = Status_ExpressionUknownOp; + break; + + case 'L': + if(line[*pos] == 'N') { + *operation = NGCUnaryOp_LN; + (*pos)++; + } else + status = Status_ExpressionUknownOp; + break; + + case 'R': + if (!strncmp(line + *pos, "OUND", 4)) { + *operation = NGCUnaryOp_Round; + *pos += 4; + } else + status = Status_ExpressionUknownOp; + break; + + case 'S': + if(!strncmp(line + *pos, "IN", 2)) { + *operation = NGCUnaryOp_SIN; + *pos += 2; + } else if(!strncmp((line + *pos), "QRT", 3)) { + *operation = NGCUnaryOp_SQRT; + *pos += 3; + } else + status = Status_ExpressionUknownOp; + break; + + case 'T': + if(!strncmp(line + *pos, "AN", 2)) { + *operation = NGCUnaryOp_TAN; + *pos += 2; + } else + status = Status_ExpressionUknownOp; + break; + + default: + status = Status_ExpressionUknownOp; + } + + return status; +} + +/*! \brief Reads the value out of a parameter of the line, starting at the +index given by the pos offset. + +According to the RS274/NGC manual [NCMS, p. 62], the characters following +# may be any "parameter expression". Thus, the following are legal +and mean the same thing (the value of the parameter whose number is +stored in parameter 2): + ##2 + #[#2] + +Parameter setting is done in parallel, not sequentially. For example +if #1 is 5 before the line "#1=10 #2=#1" is read, then after the line +is is executed, #1 is 10 and #2 is 5. If parameter setting were done +sequentially, the value of #2 would be 10 after the line was executed. + +\param line pointer to RS274/NGC code (block). +\param pos offset into line where expression starts. +\param value pointer to float where result is to be stored. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t read_parameter (char *line, uint_fast8_t *pos, float *value, bool check) +{ + status_code_t status = Status_BadNumberFormat; + + if(*(line + *pos) == '#') { + + (*pos)++; + + if(*(line + *pos) == '<') { + + (*pos)++; + char *param = line + *pos, *arg = line + *pos; + + while(*arg && *arg != '>') + arg++; + + *pos += arg - param + 1; + + if(*arg == '>') { + *arg = '\0'; + if(ngc_named_param_get(param, value)) + status = Status_OK; + *arg = '>'; + } + + } else if(read_float(line, pos, value)) { + if(ngc_param_get((ngc_param_id_t)*value, value)) + status = Status_OK; + } + } + + return status; +} + +/*! \brief Reads a slash and the second argument to the ATAN function, +starting at the index given by the pos offset. Then it computes the value +of the ATAN operation applied to the two arguments. + +\param line pointer to RS274/NGC code (block). +\param pos offset into line where expression starts. +\param value pointer to float where result is to be stored. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t read_atan (char *line, uint_fast8_t *pos, float *value) +{ + float argument2; + + if(line[*pos] != '/') + return Status_ExpressionSyntaxError; // Slash missing after first ATAN argument + + (*pos)++; + + if(line[*pos] != '[') + return Status_ExpressionSyntaxError; // Left bracket missing after slash with ATAN; + + status_code_t status; + + if((status = ngc_eval_expression(line, pos, &argument2)) == Status_OK) + *value = atan2f(*value, argument2) * DEGRAD; /* value in radians, convert to degrees */ + + return status; +} + +/*! \brief Reads the value out of an unary operation of the line, starting at the +index given by the pos offset. The ATAN operation is +handled specially because it is followed by two arguments. + +\param line pointer to RS274/NGC code (block). +\param pos offset into line where expression starts. +\param value pointer to float where result is to be stored. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t read_unary (char *line, uint_fast8_t *pos, float *value) +{ + ngc_unary_op_t operation; + status_code_t status; + + if((status = read_operation_unary(line, pos, &operation)) == Status_OK) { + + if(line[*pos] != '[') + status = Status_ExpressionSyntaxError; // Left bracket missing after unary operation name + else { + + /* + if (operation == NGCUnaryOp_Exists) { + CHP(read_bracketed_parameter(line, pos, value, true)); + return Status_OK; + } + */ + if((status = ngc_eval_expression(line, pos, value)) == Status_OK) { + if(operation == NGCUnaryOp_ATAN) + status = read_atan(line, pos, value); + else + status = execute_unary(value, operation); + } + } + } + + return status; +} + +/*! \brief Reads a real value out of the line, starting at the +index given by the pos offset. The value may be a number, a parameter +value, a unary function, or an expression. It calls one of four +other readers, depending upon the first character. + +\param line pointer to RS274/NGC code (block). +\param pos offset into line where expression starts. +\param value pointer to float where result is to be stored. +\returns #Status_OK enum value if processed without error, appropriate \ref status_code_t enum value if not. +*/ +static status_code_t read_real_value (char *line, uint_fast8_t *pos, float *value) +{ + char c = line[*pos], c1; + + if(c == '\0') + return Status_ExpressionSyntaxError; // No characters found when reading real value + + status_code_t status; + + c1 = line[*pos + 1]; + + if(c == '[') + status = ngc_eval_expression(line, pos, value); + else if(c == '#') + status = read_parameter(line, pos, value, false); + else if(c == '+' && c1 && !isdigit(c1) && c1 != '.') { + (*pos)++; + status = read_real_value(line, pos, value); + } else if(c == '-' && c1 && !isdigit(c1) && c1 != '.') { + (*pos)++; + status = read_real_value(line, pos, value); + *value = -*value; + } else if ((c >= 'A') && (c <= 'Z')) + status = read_unary(line, pos, value); + else + status = (read_float(line, pos, value) ? Status_OK : Status_BadNumberFormat); + + if(isnanf(*value)) + status = Status_ExpressionInvalidResult; // Calculation resulted in 'not a number' + else if(isinff(*value)) + status = Status_ExpressionInvalidResult; // Calculation resulted in 'not a number' + + return status; +} + +/*! \brief Evaluate expression and set result if successful. + +\param line pointer to RS274/NGC code (block). +\param pos offset into line where expression starts. +\param value pointer to float where result is to be stored. +\returns #Status_OK enum value if evaluated without error, appropriate \ref status_code_t enum value if not. +*/ +status_code_t ngc_eval_expression (char *line, uint_fast8_t *pos, float *value) +{ + float values[MAX_STACK]; + ngc_binary_op_t operators[MAX_STACK]; + uint_fast8_t stack_index = 1; + + if(line[*pos] != '[') + return Status_GcodeUnsupportedCommand; + + (*pos)++; + + status_code_t status; + + if((status = read_real_value(line, pos, values)) != Status_OK) + return status; + + if((status = read_operation(line, pos, operators)) != Status_OK) + return status; + + for(; operators[0] != NGCBinaryOp_RightBracket;) { + + if((status = read_real_value(line, pos, values + stack_index)) != Status_OK) + return status; + + if((status = read_operation(line, pos, operators + stack_index)) != Status_OK) + return status; + + if (precedence(operators[stack_index]) > precedence(operators[stack_index - 1])) + stack_index++; + else { // precedence of latest operator is <= previous precedence + for(; precedence(operators[stack_index]) <= precedence(operators[stack_index - 1]);) { + + if((status = execute_binary(values + stack_index - 1, operators[stack_index - 1], values + stack_index)) != Status_OK) + return status; + + operators[stack_index - 1] = operators[stack_index]; + if((stack_index > 1) && precedence(operators[stack_index - 1] <= precedence(operators[stack_index - 2]))) + stack_index--; + else + break; + } + } + } + + *value = values[0]; + + return Status_OK; +} + +#endif diff --git a/ngc_expr.h b/ngc_expr.h new file mode 100644 index 0000000..584e37a --- /dev/null +++ b/ngc_expr.h @@ -0,0 +1,8 @@ +/* ngc_expr.h */ + +#ifndef _NGC_EXPR_H_ +#define _NGC_EXPR_H_ + +status_code_t ngc_eval_expression (char *line, uint_fast8_t *pos, float *value); + +#endif diff --git a/ngc_params.c b/ngc_params.c new file mode 100644 index 0000000..afce94e --- /dev/null +++ b/ngc_params.c @@ -0,0 +1,662 @@ +/* + ngc_params.c - get/set NGC parameter value by id or name + + Part of grblHAL + + Copyright (c) 2021 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 . +*/ + +/* + All predefined parameters defined in NIST RS274NGC version 3 (ref section 3.2.1) are implemented. + Most additional predefined parameters defined by LinuxCNC (ref section 5.2.3.1) are implemented. +*/ + +#include +#include +#include +#include + +#include "system.h" +#include "settings.h" +#include "ngc_params.h" + +#define MAX_PARAM_LENGTH 20 + +typedef float (*ngc_param_get_ptr)(ngc_param_id_t id); +typedef float (*ngc_named_param_get_ptr)(void); + +typedef struct { + ngc_param_id_t id_min; + ngc_param_id_t id_max; + ngc_param_get_ptr get; +} ngc_ro_param_t; + +typedef struct ngc_rw_param { + ngc_param_id_t id; + float value; + struct ngc_rw_param *next; +} ngc_rw_param_t; + +typedef struct { + const char *name; + ngc_named_param_get_ptr get; +} ngc_named_ro_param_t; + +typedef struct ngc_named_rw_param { + char name[MAX_PARAM_LENGTH + 1]; + float value; + struct ngc_named_rw_param *next; +} ngc_named_rw_param_t; + +ngc_rw_param_t *rw_params = NULL; +ngc_named_rw_param_t *rw_global_params = NULL; + +static float _relative_pos (uint_fast8_t axis) +{ + float value = sys.position[axis] / settings.axis[axis].steps_per_mm - gc_get_offset(axis); + if(settings.flags.report_inches) + value *= 25.4f; + + return value; +} + +// numbered parameters + +static float probe_coord (ngc_param_id_t id) +{ + uint_fast8_t axis = id % 10; + + return axis <= N_AXIS ? sys.probe_position[axis - 1] : 0.0f; +} + +static float scaling_factors (ngc_param_id_t id) +{ + float *factors = gc_get_scaling(); + uint_fast8_t axis = id % 10; + + return axis <= N_AXIS ? factors[axis - 1] : 0.0f; +} + +static float probe_result (ngc_param_id_t id) +{ + return sys.flags.probe_succeeded ? 1.0f : 0.0f; +} +/* +static float home_pos (ngc_param_id_t id) +{ + uint_fast8_t axis = id % 10; + + return axis <= N_AXIS ? sys.home_position[axis - 1] : 0.0f; +} +*/ +static float m66_result (ngc_param_id_t id) +{ + return (float)sys.var5933; +} + +static float tool_number (ngc_param_id_t id) +{ + return (float)gc_state.tool->tool; +} + +static float tool_offset (ngc_param_id_t id) +{ + uint_fast8_t axis = id % 10; + + return axis <= N_AXIS ? gc_state.tool_length_offset[axis] : 0.0f; +} + +static float g28_home (ngc_param_id_t id) +{ + float value = 0.0f; + uint_fast8_t axis = id % 10; + coord_system_t data; + + if(axis <= N_AXIS && settings_read_coord_data(CoordinateSystem_G28, &data.xyz)) + value = data.xyz[axis - 1]; + + return value; +} + +static float g30_home (ngc_param_id_t id) +{ + float value = 0.0f; + uint_fast8_t axis = id % 10; + coord_system_t data; + +#if COMPATIBILITY_LEVEL > 1 + if(id <= CoordinateSystem_G59) { +#endif + if (axis <= N_AXIS && settings_read_coord_data(CoordinateSystem_G30, &data.xyz)) + value = data.xyz[axis - 1]; +#if COMPATIBILITY_LEVEL > 1 + } +#endif + + return value; +} + +static float coord_system (ngc_param_id_t id) +{ + return (float)gc_state.modal.coord_system.id; +} + +static float coord_system_offset (ngc_param_id_t id) +{ + float value = 0.0f; + uint_fast8_t axis = id % 10; + coord_system_t data; + + id = (id - 5220 - axis - (id == 0 ? 10 : 0)) / 20; + + if (axis > 0 && axis <= N_AXIS && settings_read_coord_data((coord_system_id_t)id, &data.xyz)) + value = data.xyz[axis - 1]; + + return value; +} + +static float g92_offset_applied (ngc_param_id_t id) +{ + return (float)gc_state.g92_coord_offset_applied; +} + +static float g92_offset (ngc_param_id_t id) +{ + uint_fast8_t axis = id % 10; + + return axis <= N_AXIS ? gc_state.g92_coord_offset [axis - 1] : 0.0f; +} + +static float work_position (ngc_param_id_t id) +{ + float value = 0.0f; + uint_fast8_t axis = id % 10; + + if(axis < N_AXIS) + value = _relative_pos(axis); + + return value; +} + +PROGMEM static const ngc_ro_param_t ngc_ro_params[] = { + { .id_min = 5061, .id_max = 5069, .get = probe_coord }, // LinuxCNC + { .id_min = 5070, .id_max = 5070, .get = probe_result }, // LinuxCNC + { .id_min = 5161, .id_max = 5169, .get = g28_home }, + { .id_min = 5181, .id_max = 5189, .get = g30_home }, + { .id_min = 5191, .id_max = 5199, .get = scaling_factors }, // Mach3 + { .id_min = 5210, .id_max = 5210, .get = g92_offset_applied }, // LinuxCNC + { .id_min = 5211, .id_max = 5219, .get = g92_offset }, + { .id_min = 5220, .id_max = 5220, .get = coord_system }, + { .id_min = 5221, .id_max = 5230, .get = coord_system_offset }, + { .id_min = 5241, .id_max = 5250, .get = coord_system_offset }, + { .id_min = 5261, .id_max = 5270, .get = coord_system_offset }, + { .id_min = 5281, .id_max = 5290, .get = coord_system_offset }, + { .id_min = 5301, .id_max = 5310, .get = coord_system_offset }, + { .id_min = 5321, .id_max = 5230, .get = coord_system_offset }, + { .id_min = 5341, .id_max = 5350, .get = coord_system_offset }, + { .id_min = 5361, .id_max = 5370, .get = coord_system_offset }, + { .id_min = 5381, .id_max = 5390, .get = coord_system_offset }, + { .id_min = 5399, .id_max = 5399, .get = m66_result }, // LinuxCNC + { .id_min = 5400, .id_max = 5400, .get = tool_number }, // LinuxCNC + { .id_min = 5401, .id_max = 5409, .get = tool_offset }, // LinuxCNC + { .id_min = 5420, .id_max = 5428, .get = work_position } // LinuxCNC +}; + +bool ngc_param_get (ngc_param_id_t id, float *value) +{ + bool found = id > 0 && id < ngc_ro_params[0].id_min; + uint_fast8_t idx = sizeof(ngc_ro_params) / sizeof(ngc_ro_param_t); + + *value = 0.0f; + + if(found) { + ngc_rw_param_t *rw_param = rw_params; + while(rw_param) { + if(rw_param->id == id) { + *value = rw_param->value; + rw_param = NULL; + } else + rw_param = rw_param->next; + } + } else do { + idx--; + if((found = id >= ngc_ro_params[idx].id_min && id <= ngc_ro_params[idx].id_max)) + *value = ngc_ro_params[idx].get(id); + } while(idx && !found); + + return found; +} + +bool ngc_param_is_rw (ngc_param_id_t id) +{ + return id > 0 && id < ngc_ro_params[0].id_min; +} + +bool ngc_param_exists (ngc_param_id_t id) +{ + return id > 0 && id <= ngc_ro_params[(sizeof(ngc_ro_params) / sizeof(ngc_ro_param_t)) - 1].id_max; +} + +bool ngc_param_set (ngc_param_id_t id, float value) +{ + bool ok = id > 0 && id < ngc_ro_params[0].id_min; + + if(ok) { + + ngc_rw_param_t *rw_param = rw_params, *rw_param_last = rw_params; + + while(rw_param) { + if(rw_param->id == id) { + break; + } else { + rw_param_last = rw_param; + rw_param = rw_param->next; + } + } + + if(rw_param == NULL && value != 0.0f && (rw_param = malloc(sizeof(ngc_rw_param_t)))) { + rw_param->id = id; + rw_param->next = NULL; + if(rw_params == NULL) + rw_params = rw_param; + else + rw_param_last->next = rw_param; + } + + if(rw_param) + rw_param->value = value; + else + ok = value == 0.0f; + } + + return ok; +} + +// Named parameters + +static float _line (void) +{ + return (float)gc_state.line_number; +} + +static float _motion_mode (void) +{ + return (float)(gc_state.modal.motion * 10); // TODO: Fix G38.x +} + +static float _plane (void) +{ + return (float)(170 + gc_state.modal.plane_select * 10); +} + +static float _ccomp (void) +{ + return 400.0f; +} + +static float _metric (void) +{ + return gc_state.modal.units_imperial ? 0.0f : 1.0f; +} + +static float _imperial (void) +{ + return gc_state.modal.units_imperial ? 1.0f : 0.0f; +} + +static float _absolute (void) +{ + return gc_state.modal.distance_incremental ? 0.0f : 1.0f; +} + +static float _incremental (void) +{ + return gc_state.modal.distance_incremental ? 1.0f : 0.0f; +} + +static float _inverse_time (void) +{ + return gc_state.modal.feed_mode == FeedMode_InverseTime ? 1.0f : 0.0f; +} + +static float _units_per_minute (void) +{ + return gc_state.modal.feed_mode == FeedMode_UnitsPerMin ? 1.0f : 0.0f; +} + +static float _units_per_rev (void) +{ + return gc_state.modal.feed_mode == FeedMode_UnitsPerRev ? 1.0f : 0.0f; +} + +static float _coord_system (void) +{ + uint_fast16_t id = gc_state.modal.coord_system.id * 10; + + if(id > (CoordinateSystem_G59 * 10)) + id = (CoordinateSystem_G59 * 10) + gc_state.modal.coord_system.id - CoordinateSystem_G59; + + return (float)(540 + id); +} + +static float _tool_offset (void) +{ + return gc_state.modal.tool_offset_mode >= ToolLengthOffset_Enable ? 1.0f : 0.0f; +} + +static float _retract_r_plane (void) +{ + return gc_state.modal.retract_mode == CCRetractMode_Previous ? 1.0f : 0.0f; +} + +static float _retract_old_z (void) +{ + return gc_state.modal.retract_mode == CCRetractMode_RPos ? 1.0f : 0.0f; +} + +static float _spindle_rpm_mode (void) +{ + return gc_state.modal.spindle_rpm_mode == SpindleSpeedMode_RPM ? 1.0f : 0.0f; +} + +static float _spindle_css_mode (void) +{ + return gc_state.modal.spindle_rpm_mode == SpindleSpeedMode_CSS ? 1.0f : 0.0f; +} + +static float _lathe_diameter_mode (void) +{ + return gc_state.modal.diameter_mode ? 1.0f : 0.0f; +} + +static float _lathe_radius_mode (void) +{ + return gc_state.modal.diameter_mode ? 0.0f : 1.0f; +} + +static float _spindle_on (void) +{ + return gc_state.modal.spindle.on ? 1.0f : 0.0f; +} + +static float _spindle_cw (void) +{ + return gc_state.modal.spindle.ccw ? 1.0f : 0.0f; +} + +static float _mist (void) +{ + return gc_state.modal.coolant.mist ? 1.0f : 0.0f; +} + +static float _flood (void) +{ + return gc_state.modal.coolant.flood ? 1.0f : 0.0f; +} + +static float _speed_override (void) +{ + return gc_state.modal.override_ctrl.spindle_rpm_disable ? 0.0f : 1.0f; +} + +static float _feed_override (void) +{ + return gc_state.modal.override_ctrl.feed_rate_disable ? 0.0f : 1.0f; +} + +static float _feed_hold (void) +{ + return gc_state.modal.override_ctrl.feed_hold_disable ? 0.0f : 1.0f; +} + +static float _feed (void) +{ + return gc_state.feed_rate; +} + +static float _rpm (void) +{ + return gc_state.spindle.rpm; +} + +static float _x (void) +{ + return _relative_pos(X_AXIS); +} + +static float _y (void) +{ + return _relative_pos(Y_AXIS); +} + +static float _z (void) +{ + return _relative_pos(Z_AXIS); +} + +static float _a (void) +{ +#ifdef A_AXIS + return _relative_pos(A_AXIS); +#else + return 0.0f; +#endif +} + +static float _b (void) +{ +#ifdef B_AXIS + return _relative_pos(B_AXIS); +#else + return 0.0f; +#endif +} + +static float _c (void) +{ +#ifdef C_AXIS + return _relative_pos(C_AXIS); +#else + return 0.0f; +#endif +} + +static float _current_tool (void) +{ + return (float)gc_state.tool->tool; +} + +static float _selected_tool (void) +{ + return gc_state.tool_change ? (float)gc_state.tool_pending : -1.0f; +} + +static float _false (void) +{ + return 0.0f; +} + +static float _true (void) +{ + return 1.0f; +} + +PROGMEM static const ngc_named_ro_param_t ngc_named_ro_param[] = { + { .name = "_vmajor", .get = _true }, + { .name = "_vminor", .get = _true }, + { .name = "_line", .get = _line }, + { .name = "_motion_mode", .get = _motion_mode }, + { .name = "_plane", .get = _plane }, + { .name = "_ccomp", .get = _ccomp }, + { .name = "_metric", .get = _metric }, + { .name = "_imperial", .get = _imperial }, + { .name = "_absolute", .get = _absolute }, + { .name = "_incremental", .get = _incremental }, + { .name = "_inverse_time", .get = _inverse_time }, + { .name = "_units_per_minute", .get = _units_per_minute }, + { .name = "_units_per_rev", .get = _units_per_rev }, + { .name = "_coord_system", .get = _coord_system }, + { .name = "_tool_offset", .get = _tool_offset }, + { .name = "_retract_r_plane", .get = _retract_r_plane }, + { .name = "_retract_old_z", .get = _retract_old_z }, + { .name = "_spindle_rpm_mode", .get = _spindle_rpm_mode }, + { .name = "_spindle_css_mode", .get = _spindle_css_mode }, + { .name = "_ijk_absolute_mode", .get = _false }, + { .name = "_lathe_diameter_mode", .get = _lathe_diameter_mode }, + { .name = "_lathe_radius_mode", .get = _lathe_radius_mode }, + { .name = "_spindle_on", .get = _spindle_on }, + { .name = "_spindle_cw", .get = _spindle_cw }, + { .name = "_mist", .get = _mist }, + { .name = "_flood", .get = _flood }, + { .name = "_speed_override", .get = _speed_override }, + { .name = "_feed_override", .get = _feed_override }, + { .name = "_adaptive_feed", .get = _false }, + { .name = "_feed_hold", .get = _feed_hold }, + { .name = "_feed", .get = _feed }, + { .name = "_rpm", .get = _rpm }, + { .name = "_x", .get = _x }, // = 5420 + { .name = "_y", .get = _y }, // = 5421 + { .name = "_z", .get = _z }, // = 5422 + { .name = "_a", .get = _a }, // = 5423 + { .name = "_b", .get = _b }, // = 5424 + { .name = "_c", .get = _c }, // = 5425 + { .name = "_u", .get = _false }, // = 5426 + { .name = "_v", .get = _false }, // = 5427 + { .name = "_w", .get = _false }, // = 5428 + { .name = "_current_tool", .get = _current_tool }, + { .name = "_current_pocket", .get = _false }, + { .name = "_selected_tool", .get = _selected_tool }, + { .name = "_selected_pocket", .get = _false } +}; + +bool ngc_named_param_get (char *name, float *value) +{ + char c, *s = name; + bool found = false; + uint_fast8_t idx = sizeof(ngc_named_ro_param) / sizeof(ngc_named_ro_param_t); + + // Lowercase name + while((c = *s)) + *s++ = LCAPS(c); + + *value = 0.0f; + + if(*name == '_') do { + idx--; + if((found = !strcmp(name, ngc_named_ro_param[idx].name))) + *value = ngc_named_ro_param[idx].get(); + } while(idx && !found); + + if(!found) { + ngc_named_rw_param_t *rw_param = rw_global_params; + while(rw_param && !found) { + if((found = !strcmp(rw_param->name, name))) + *value = rw_param->value; + else + rw_param = rw_param->next; + } + } + + return found; +} + +bool ngc_named_param_exists (char *name) +{ + char c, *s1 = name, *s2 = name; + bool ok = false; + uint_fast8_t idx = sizeof(ngc_named_ro_param) / sizeof(ngc_named_ro_param_t); + + // Lowercase name, remove control characters and spaces + while((c = *s1++) && c > ' ') + *s2++ = LCAPS(c); + + *s2 = '\0'; + + // Check if name is supplied, return false if not. + if((*name == '_' ? *(name + 1) : *name) == '\0') + + // Check if it is a (read only) predefined parameter. + if(*name == '_') do { + idx--; + ok = !strcmp(name, ngc_named_ro_param[idx].name); + } while(idx && !ok); + + // If not predefined attempt to find it. + if(!ok && rw_global_params && strlen(name) < MAX_PARAM_LENGTH) { + + ngc_named_rw_param_t *rw_param = rw_global_params; + + while(rw_param) { + if((ok = !strcmp(rw_param->name, name))) + break; + rw_param = rw_param->next; + } + } + + return ok; +} + +bool ngc_named_param_set (char *name, float value) +{ + char c, *s1 = name, *s2 = name; + bool ok = false; + uint_fast8_t idx = sizeof(ngc_named_ro_param) / sizeof(ngc_named_ro_param_t); + + // Lowercase name, remove control characters and spaces + while((c = *s1++) && c > ' ') + *s2++ = LCAPS(c); + + *s2 = '\0'; + + // Check if name is supplied, return false if not. + if((*name == '_' ? *(name + 1) : *name) == '\0') + return false; + + // Check if it is a (read only) predefined parameter. + if(*name == '_') do { + idx--; + ok = !strcmp(name, ngc_named_ro_param[idx].name); + } while(idx && !ok); + + // If not predefined attempt to set it. + if(!ok && (ok = strlen(name) < MAX_PARAM_LENGTH)) { + + ngc_named_rw_param_t *rw_param = rw_global_params, *rw_param_last = rw_global_params; + + while(rw_param) { + if(!strcmp(rw_param->name, name)) { + break; + } else { + rw_param_last = rw_param; + rw_param = rw_param->next; + } + } + + if(rw_param == NULL && (rw_param = malloc(sizeof(ngc_named_rw_param_t)))) { + strcpy(rw_param->name, name); + rw_param->next = NULL; + if(rw_global_params == NULL) + rw_global_params = rw_param; + else + rw_param_last->next = rw_param; + } + + if((ok = rw_param != NULL)) + rw_param->value = value; + } + + return ok; +} diff --git a/ngc_params.h b/ngc_params.h new file mode 100644 index 0000000..d4517ff --- /dev/null +++ b/ngc_params.h @@ -0,0 +1,46 @@ +/* + ngc_params.c - get/set NGC parameter value by id or name + + Part of grblHAL + + Copyright (c) 2021 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 . +*/ + +/* + All predefined parameters defined in NIST RS274NGC version 3 (ref section 3.2.1) are implemented. + Most additional predefined parameters defined by LinuxCNC (ref section 5.2.3.1) are implemented. + Currently it is not possible to set any parameters or reference them from gcode. +*/ + +#ifndef _NGC_PARAMS_H_ +#define _NGC_PARAMS_H_ + +typedef uint16_t ngc_param_id_t; + +typedef struct { + ngc_param_id_t id; + float value; +} ngc_param_t; + +bool ngc_param_get (ngc_param_id_t id, float *value); +bool ngc_param_set (ngc_param_id_t id, float value); +bool ngc_param_is_rw (ngc_param_id_t id); +bool ngc_param_exists (ngc_param_id_t id); +bool ngc_named_param_get (char *name, float *value); +bool ngc_named_param_set (char *name, float value); +bool ngc_named_param_exists (char *name); + +#endif diff --git a/nuts_bolts.h b/nuts_bolts.h index ef5c255..e03a9af 100644 --- a/nuts_bolts.h +++ b/nuts_bolts.h @@ -39,13 +39,17 @@ #define M_PI 3.14159265358979323846f #endif +#define TOLERANCE_EQUAL 0.0001f + #define TAN_30 0.57735f // Used for threading calculations (60 degree inserts) #define RADDEG 0.0174532925f // Radians per degree +#define DEGRAD 57.29577951f // Degrees per radians #define ABORTED (sys.abort || sys.cancel) // Convert character to uppercase -#define CAPS(c) ((c >= 'a' && c <= 'z') ? c & 0x5F : c) +#define CAPS(c) ((c >= 'a' && c <= 'z') ? (c & 0x5F) : c) +#define LCAPS(c) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c) #ifndef STM32F103xB #ifndef UNUSED diff --git a/nvs.h b/nvs.h index 8c20759..18c000e 100644 --- a/nvs.h +++ b/nvs.h @@ -33,7 +33,7 @@ Minimum 1024 bytes required, more if space for driver and/or plugin data and set /*! \brief Number of bytes at the start of the NVS area reserved for core settings and parameters. Minimum 1024 bytes required. */ -#define GRBL_NVS_SIZE 1024 +#define GRBL_NVS_END 1023 //! Number of bytes used for storing CRC values. Do not change this! #define NVS_CRC_BYTES 1 @@ -50,7 +50,10 @@ __NOTE:__ 1024 bytes of persistent storage is the minimum required. #define NVS_ADDR_BUILD_INFO 942U #define NVS_ADDR_STARTUP_BLOCK (NVS_ADDR_BUILD_INFO - 1 - N_STARTUP_LINE * (sizeof(stored_line_t) + NVS_CRC_BYTES)) #ifdef N_TOOLS -#define NVS_ADDR_TOOL_TABLE (NVS_ADDR_PARAMETERS - 1 - N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES)) +#define NVS_ADDR_TOOL_TABLE (GRBL_NVS_END + 1) +#define GRBL_NVS_SIZE (GRBL_NVS_END + 1 + N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES)) +#else +#define GRBL_NVS_SIZE (GRBL_NVS_END + 1) #endif ///@} diff --git a/nvs_buffer.c b/nvs_buffer.c index 87f0fc6..69a0935 100644 --- a/nvs_buffer.c +++ b/nvs_buffer.c @@ -32,6 +32,9 @@ #include "hal.h" #include "nvs_buffer.h" #include "protocol.h" +#include "settings.h" +#include "gcode.h" +#include "nvs.h" static uint8_t *nvsbuffer = NULL; static nvs_io_t physical_nvs; @@ -51,6 +54,7 @@ typedef struct { #define NVS_GROUP_STARTUP 3 #define NVS_GROUP_BUILD 4 + #define PARAMETER_ADDR(n) (NVS_ADDR_PARAMETERS + n * (sizeof(coord_data_t) + NVS_CRC_BYTES)) #define STARTLINE_ADDR(n) (NVS_ADDR_STARTUP_BLOCK + n * (sizeof(stored_line_t) + NVS_CRC_BYTES)) #ifdef N_TOOLS @@ -59,19 +63,7 @@ typedef struct { static const emap_t target[] = { {NVS_ADDR_GLOBAL, NVS_GROUP_GLOBAL, 0}, -#ifdef N_TOOLS - {TOOL_ADDR(0), NVS_GROUP_TOOLS, 0}, - {TOOL_ADDR(1), NVS_GROUP_TOOLS, 1}, - {TOOL_ADDR(2), NVS_GROUP_TOOLS, 2}, - {TOOL_ADDR(3), NVS_GROUP_TOOLS, 3}, - {TOOL_ADDR(4), NVS_GROUP_TOOLS, 4}, - {TOOL_ADDR(5), NVS_GROUP_TOOLS, 5}, - {TOOL_ADDR(6), NVS_GROUP_TOOLS, 6}, - {TOOL_ADDR(7), NVS_GROUP_TOOLS, 7}, -#if N_TOOLS > 8 -#error Increase number of tool entries! -#endif -#endif + {PARAMETER_ADDR(0), NVS_GROUP_PARAMETERS, 0}, {PARAMETER_ADDR(1), NVS_GROUP_PARAMETERS, 1}, {PARAMETER_ADDR(2), NVS_GROUP_PARAMETERS, 2}, @@ -90,6 +82,29 @@ static const emap_t target[] = { #error Increase number of startup line entries! #endif {NVS_ADDR_BUILD_INFO, NVS_GROUP_BUILD, 0}, +#ifdef N_TOOLS + {TOOL_ADDR(0), NVS_GROUP_TOOLS, 0}, + {TOOL_ADDR(1), NVS_GROUP_TOOLS, 1}, + {TOOL_ADDR(2), NVS_GROUP_TOOLS, 2}, + {TOOL_ADDR(3), NVS_GROUP_TOOLS, 3}, + {TOOL_ADDR(4), NVS_GROUP_TOOLS, 4}, + {TOOL_ADDR(5), NVS_GROUP_TOOLS, 5}, + {TOOL_ADDR(6), NVS_GROUP_TOOLS, 6}, + {TOOL_ADDR(7), NVS_GROUP_TOOLS, 7}, +#if N_TOOLS > 8 + {TOOL_ADDR(8), NVS_GROUP_TOOLS, 8}, + {TOOL_ADDR(9), NVS_GROUP_TOOLS, 9}, + {TOOL_ADDR(10), NVS_GROUP_TOOLS, 10}, + {TOOL_ADDR(11), NVS_GROUP_TOOLS, 11}, + {TOOL_ADDR(12), NVS_GROUP_TOOLS, 12}, + {TOOL_ADDR(13), NVS_GROUP_TOOLS, 13}, + {TOOL_ADDR(14), NVS_GROUP_TOOLS, 14}, + {TOOL_ADDR(15), NVS_GROUP_TOOLS, 15}, +#endif +#if N_TOOLS > 16 +#error Increase number of tool entries! +#endif +#endif {0, 0, 0} // list termination - do not remove }; @@ -106,8 +121,6 @@ inline static void ram_put_byte (uint32_t addr, uint8_t new_value) nvsbuffer[addr] = new_value; } -// Extensions added as part of Grbl - static nvs_transfer_result_t memcpy_to_ram (uint32_t destination, uint8_t *source, uint32_t size, bool with_checksum) { if(hal.nvs.driver_area.address && destination > hal.nvs.driver_area.address + hal.nvs.driver_area.size) @@ -373,14 +386,6 @@ void nvs_memmap (void) strcat(buf, uitoa(sizeof(settings_t) + NVS_CRC_BYTES)); report_message(buf, Message_Plain); -#ifdef N_TOOLS - strcpy(buf, "Tool table: "); - strcat(buf, uitoa(NVS_ADDR_TOOL_TABLE)); - strcat(buf, " "); - strcat(buf, uitoa(N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES))); - report_message(buf, Message_Plain); -#endif - strcpy(buf, "Parameters: "); strcat(buf, uitoa(NVS_ADDR_PARAMETERS)); strcat(buf, " "); @@ -399,6 +404,14 @@ void nvs_memmap (void) strcat(buf, uitoa(sizeof(stored_line_t) + NVS_CRC_BYTES)); report_message(buf, Message_Plain); +#ifdef N_TOOLS + strcpy(buf, "Tool table: "); + strcat(buf, uitoa(NVS_ADDR_TOOL_TABLE)); + strcat(buf, " "); + strcat(buf, uitoa(N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES))); + report_message(buf, Message_Plain); +#endif + strcpy(buf, "Driver: "); strcat(buf, uitoa(hal.nvs.driver_area.address)); strcat(buf, " "); diff --git a/nvs_buffer.h b/nvs_buffer.h index 9978c8b..f306b89 100644 --- a/nvs_buffer.h +++ b/nvs_buffer.h @@ -34,8 +34,12 @@ typedef struct { uint8_t startup_lines; uint16_t coord_data; #ifdef N_TOOLS +#if N_TOOLS > 16 + uint32_t tool_data; +#else uint16_t tool_data; #endif +#endif } settings_dirty_t; extern settings_dirty_t settings_dirty; diff --git a/protocol.c b/protocol.c index 87be863..38b0faf 100644 --- a/protocol.c +++ b/protocol.c @@ -45,18 +45,10 @@ typedef union { uint8_t overflow :1, comment_parentheses :1, comment_semicolon :1, - block_delete :1, - unassigned :4; + unassigned :5; }; } line_flags_t; -typedef struct { - char *message; - uint_fast8_t idx; - uint_fast8_t tracker; - bool show; -} user_message_t; - typedef struct { volatile uint_fast8_t head; volatile uint_fast8_t tail; @@ -67,8 +59,6 @@ static uint_fast16_t char_counter = 0; static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. static char xcommand[LINE_BUFFER_SIZE]; static bool keep_rt_commands = false; -static user_message_t user_message = {NULL, 0, 0, false}; -static const char *msg = "(MSG,"; static realtime_queue_t realtime_queue = {0}; static void protocol_exec_rt_suspend (); @@ -163,10 +153,9 @@ bool protocol_main_loop (void) int16_t c; char eol = '\0'; line_flags_t line_flags = {0}; - bool nocaps = false, line_is_comment = false; xcommand[0] = '\0'; - user_message.show = keep_rt_commands = false; + keep_rt_commands = false; while(true) { @@ -177,7 +166,7 @@ bool protocol_main_loop (void) if(c == ASCII_CAN) { eol = xcommand[0] = '\0'; - keep_rt_commands = nocaps = line_is_comment = user_message.show = false; + keep_rt_commands = false; char_counter = line_flags.value = 0; gc_state.last_error = Status_OK; @@ -206,7 +195,7 @@ bool protocol_main_loop (void) // Direct and execute one line of formatted input, and report status of execution. if (line_flags.overflow) // Report line overflow error. gc_state.last_error = Status_Overflow; - else if ((line[0] == '\0' || char_counter == 0) && !user_message.show && !line_is_comment) // Empty or comment line. For syncing purposes. + else if(line[0] == '\0') // Empty line. For syncing purposes. gc_state.last_error = Status_OK; else if (line[0] == '$') {// Grbl '$' system command if((gc_state.last_error = system_execute_line(line)) == Status_LimitsEngaged) { @@ -223,7 +212,7 @@ bool protocol_main_loop (void) else { // Parse and execute g-code block. #endif - gc_state.last_error = gc_execute_block(line, user_message.show ? user_message.message : NULL); + gc_state.last_error = gc_execute_block(line); } // Add a short delay for each block processed in Check Mode to @@ -238,74 +227,39 @@ bool protocol_main_loop (void) grbl.report.status_message(gc_state.last_error); // Reset tracking data for next line. - keep_rt_commands = nocaps = user_message.show = false; + keep_rt_commands = false; char_counter = line_flags.value = 0; - } else if (c <= (nocaps ? ' ' - 1 : ' ') || line_flags.value) { - // Throw away all whitepace, control characters, comment characters and overflow characters. - if(c >= ' ' && line_flags.comment_parentheses) { - if(user_message.tracker == 5) - user_message.message[user_message.idx++] = c == ')' ? '\0' : c; - else if(user_message.tracker > 0 && CAPS(c) == msg[user_message.tracker]) - user_message.tracker++; - else - user_message.tracker = 0; - if (c == ')') { - // End of '()' comment. Resume line. - line_flags.comment_parentheses = Off; - keep_rt_commands = false; - user_message.show = user_message.show || user_message.tracker == 5; - } - } - } else { + } else if (c <= (char_counter > 0 ? ' ' - 1 : ' ')) + continue; // Strip control characters and leading whitespace. + else { switch(c) { - case '/': - if(char_counter == 0) - line_flags.block_delete = sys.flags.block_delete_enabled; - break; - case '$': case '[': - // Do not uppercase system or user commands - will destroy passwords etc... if(char_counter == 0) - nocaps = keep_rt_commands = true; + keep_rt_commands = true; break; case '(': - if(char_counter == 0) - line_is_comment = On; - if(!keep_rt_commands) { - // Enable comments flag and ignore all characters until ')' or EOL unless it is a message. - // NOTE: This doesn't follow the NIST definition exactly, but is good enough for now. - // In the future, we could simply remove the items within the comments, but retain the - // comment control characters, so that the g-code parser can error-check it. - if((line_flags.comment_parentheses = !line_flags.comment_semicolon)) { - if(!hal.driver_cap.no_gcode_message_handling) { - if(user_message.message == NULL) - user_message.message = malloc(LINE_BUFFER_SIZE); - if(user_message.message) { - user_message.idx = 0; - user_message.tracker = 1; - } - } - keep_rt_commands = true; - } - } + if(!keep_rt_commands && (line_flags.comment_parentheses = !line_flags.comment_semicolon)) + keep_rt_commands = !hal.driver_cap.no_gcode_message_handling; // Suspend real-time processing of printable command characters. + break; + + case ')': + if(!line_flags.comment_semicolon) + line_flags.comment_parentheses = keep_rt_commands = false; break; case ';': - if(char_counter == 0) - line_is_comment = On; - // NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST. - if(!keep_rt_commands) { - if((line_flags.comment_semicolon = !line_flags.comment_parentheses)) - keep_rt_commands = true; + if(!line_flags.comment_parentheses) { + keep_rt_commands = false; + line_flags.comment_semicolon = On; } break; } - if (line_flags.value == 0 && !(line_flags.overflow = char_counter >= (LINE_BUFFER_SIZE - 1))) - line[char_counter++] = nocaps ? c : CAPS(c); + if(!(line_flags.overflow = char_counter >= (LINE_BUFFER_SIZE - 1))) + line[char_counter++] = c; } } @@ -317,7 +271,7 @@ bool protocol_main_loop (void) else if (state_get() & (STATE_ALARM|STATE_ESTOP|STATE_JOG)) // Everything else is gcode. Block if in alarm, eStop or jog state. grbl.report.status_message(Status_SystemGClock); else // Parse and execute g-code block. - gc_execute_block(xcommand, NULL); + gc_execute_block(xcommand); xcommand[0] = '\0'; } @@ -518,7 +472,7 @@ bool protocol_exec_rt_system (void) sync_position(); flush_override_buffers(); if(!((state_get() == STATE_ALARM) && (sys.alarm == Alarm_LimitsEngaged || sys.alarm == Alarm_HomingRequried))) - state_set(STATE_IDLE); + state_set(hal.control.get_state().safety_door_ajar ? STATE_SAFETY_DOOR : STATE_IDLE); } // Execute and print status to output stream @@ -700,9 +654,9 @@ static void protocol_exec_rt_suspend (void) // Handle spindle overrides during suspend state_suspend_manager(); - // If door closed keep issuing cycle start requests until resumed + // If door closed keep issuing door closed requests until resumed if(state_get() == STATE_SAFETY_DOOR && !hal.control.get_state().safety_door_ajar) - system_set_exec_state_flag(EXEC_CYCLE_START); + system_set_exec_state_flag(EXEC_DOOR_CLOSED); // Check for sleep conditions and execute auto-park, if timeout duration elapses. // Sleep is valid for both hold and door states, if the spindle or coolant are on or @@ -783,7 +737,7 @@ ISR_CODE bool protocol_enqueue_realtime_command (char c) break; case CMD_SAFETY_DOOR: - if(!hal.signals_cap.safety_door_ajar) { + if(state_get() != STATE_SAFETY_DOOR) { system_set_exec_state_flag(EXEC_SAFETY_DOOR); drop = true; } @@ -933,4 +887,3 @@ void protocol_execute_noop (sys_state_t state) { (void)state; } - diff --git a/regex.c b/regex.c new file mode 100644 index 0000000..bdf238c --- /dev/null +++ b/regex.c @@ -0,0 +1,49 @@ +// regex.c - A Regular Expression Matcher +// +// Code by Rob Pike, exegesis by Brian Kernighan +// +// http://genius.cat-v.org/brian-kernighan/articles/beautiful +// +// c matches any literal character c +// . matches any single character +// ^ matches the beginning of the input string +// $ matches the end of the input string +// * matches zero or more occurrences of the previous character + +#include "regex.h" + +/* match: search for regexp anywhere in text */ +int match(char *regexp, char *text) +{ + if (regexp[0] == '^') + return matchhere(regexp+1, text); + do { /* must look even if string is empty */ + if (matchhere(regexp, text)) + return 1; + } while (*text++ != '\0'); + return 0; +} + +/* matchhere: search for regexp at beginning of text */ +int matchhere(char *regexp, char *text) +{ + if (regexp[0] == '\0') + return 1; + if (regexp[1] == '*') + return matchstar(regexp[0], regexp+2, text); + if (regexp[0] == '$' && regexp[1] == '\0') + return *text == '\0'; + if (*text!='\0' && (regexp[0]=='.' || regexp[0]==*text)) + return matchhere(regexp+1, text+1); + return 0; +} + +/* matchstar: search for c*regexp at beginning of text */ +int matchstar(int c, char *regexp, char *text) +{ + do { /* a * matches zero or more instances */ + if (matchhere(regexp, text)) + return 1; + } while (*text != '\0' && (*text++ == c || c == '.')); + return 0; +} diff --git a/regex.h b/regex.h new file mode 100644 index 0000000..a975067 --- /dev/null +++ b/regex.h @@ -0,0 +1,22 @@ +// regex.h - A Regular Expression Matcher +// +// Code by Rob Pike, exegesis by Brian Kernighan +// +// http://genius.cat-v.org/brian-kernighan/articles/beautiful +// +// c matches any literal character c +// . matches any single character +// ^ matches the beginning of the input string +// $ matches the end of the input string +// * matches zero or more occurrences of the previous character + +#pragma once + +/* match: search for regexp anywhere in text */ +int match(char *regexp, char *text); + +/* matchhere: search for regexp at beginning of text */ +int matchhere(char *regexp, char *text); + +/* matchstar: search for c*regexp at beginning of text */ +int matchstar(int c, char *regexp, char *text); diff --git a/report.c b/report.c index 7f6024d..64f8e60 100644 --- a/report.c +++ b/report.c @@ -38,6 +38,7 @@ #include "nvs_buffer.h" #include "limits.h" #include "state_machine.h" +#include "regex.h" #ifdef ENABLE_SPINDLE_LINEARIZATION #include @@ -353,88 +354,82 @@ void report_grbl_help (void) hal.stream.write("[HLP:$$ $# $G $I $N $x=val $Nx=line $J=line $SLP $C $X $H $B ~ ! ? ctrl-x]" ASCII_EOL); } -#define CAPS(c) ((c >= 'a' && c <= 'z') ? c & 0x5F : c) - -static void report_group_settings (const setting_group_detail_t *groups, const uint_fast8_t n_groups, char *lcargs) +static bool report_group_settings (const setting_group_detail_t *groups, const uint_fast8_t n_groups, char *args) { + bool found = false; uint_fast8_t idx; - uint_fast8_t len = strlen(lcargs); + char c, *s, group[25]; for(idx = 0; idx < n_groups; idx++) { - if(strlen(groups[idx].name) == len) { - char *s1 = lcargs, *s2 = (char *)groups[idx].name; - while(*s1 && CAPS(*s1) == CAPS(*s2)) { - s1++; - s2++; - } - if(*s1 == '\0') { - report_settings_details(SettingsFormat_HumanReadable, Setting_SettingsAll, groups[idx].id); - break; - } + + s = group; + strncpy(group, groups[idx].name, sizeof(group)); + + // Uppercase group name + while((c = *s)) + *s++ = CAPS(c); + + if((found = matchhere(args, group))) { + hal.stream.write(ASCII_EOL "---- "); + hal.stream.write(groups[idx].name); + hal.stream.write(":" ASCII_EOL); + report_settings_details(SettingsFormat_HumanReadable, Setting_SettingsAll, groups[idx].id); + break; } } + + return found; } -status_code_t report_help (char *args, char *lcargs) +status_code_t report_help (char *args) { - setting_details_t *settings_info = settings_get_details(); - // Strip leading spaces while(*args == ' ') args++; if(*args == '\0') { - hal.stream.write("Help arguments:" ASCII_EOL); + hal.stream.write("Help topics:" ASCII_EOL); hal.stream.write(" Commands" ASCII_EOL); hal.stream.write(" Settings" ASCII_EOL); report_setting_group_details(false, " "); + } else { - if(!strncmp(args, "COMMANDS", 8)) { - hal.stream.write("$I - list system information" ASCII_EOL); - hal.stream.write("$$ - list settings" ASCII_EOL); - hal.stream.write("$# - list offsets, tool table, probing and home position" ASCII_EOL); - hal.stream.write("$G - list parser state" ASCII_EOL); - hal.stream.write("$N - list startup lines" ASCII_EOL); - if(settings.homing.flags.enabled) - hal.stream.write("$H - home configured axes" ASCII_EOL); - if(settings.homing.flags.single_axis_commands) - hal.stream.write("$H - home single axis" ASCII_EOL); - hal.stream.write("$X - unlock machine" ASCII_EOL); - hal.stream.write("$SLP - enter sleep mode" ASCII_EOL); - hal.stream.write("$HELP - help" ASCII_EOL); - hal.stream.write("$RST=* - restore/reset all" ASCII_EOL); - hal.stream.write("$RST=$ - restore default settings" ASCII_EOL); - if(settings_info->on_get_settings) - hal.stream.write("$RST=& - restore driver and plugin default settings" ASCII_EOL); -#ifdef N_TOOLS - hal.stream.write("$RST=# - reset offsets and tool data" ASCII_EOL); -#else - hal.stream.write("$RST=# - reset offsets" ASCII_EOL); -#endif + + char c, *s = args; + + // Upper case argument + while((c = *s)) + *s++ = CAPS(c); + + if(matchhere(args, "COMMANDS")) { if(grbl.on_report_command_help) grbl.on_report_command_help(); - } else if(!strncmp(args, "SETTINGS", 8)) + + } else if(matchhere(args, "SETTINGS")) report_settings_details(SettingsFormat_HumanReadable, Setting_SettingsAll, Group_All); + else { - // Strip leading spaces from lowercase version - while(*lcargs == ' ') - lcargs++; + bool found = false; + setting_details_t *settings_info = settings_get_details(); - report_group_settings(settings_info->groups, settings_info->n_groups, lcargs); + found = report_group_settings(settings_info->groups, settings_info->n_groups, args); - if(grbl.on_get_settings) { + if(!found && grbl.on_get_settings) { on_get_settings_ptr on_get_settings = grbl.on_get_settings; while(on_get_settings) { settings_info = on_get_settings(); - if(settings_info->groups) - report_group_settings(settings_info->groups, settings_info->n_groups, lcargs); + if(settings_info->groups && (found = report_group_settings(settings_info->groups, settings_info->n_groups, args))) + break; on_get_settings = settings_info->on_get_settings; } } + + if(!found) + hal.stream.write( ASCII_EOL "N/A" ASCII_EOL); } } @@ -579,6 +574,41 @@ void report_tool_offsets (void) hal.stream.write("]" ASCII_EOL); } +// Prints NIST/LinuxCNC NGC parameter value +status_code_t report_ngc_parameter (ngc_param_id_t id) +{ + float value; + + hal.stream.write("[PARAM:"); + hal.stream.write(uitoa(id)); + if(ngc_param_get(id, &value)) { + hal.stream.write("="); + hal.stream.write(ftoa(value, 3)); + } else + hal.stream.write("=N/A"); + hal.stream.write("]" ASCII_EOL); + + return Status_OK; +} + +// Prints named LinuxCNC NGC parameter value +status_code_t report_named_ngc_parameter (char *arg) +{ + float value; + + hal.stream.write("[PARAM:"); + hal.stream.write(arg); + if(ngc_named_param_get(arg, &value)) { + hal.stream.write("="); + hal.stream.write(ftoa(value, 3)); + } else + hal.stream.write("=N/A"); + hal.stream.write("]" ASCII_EOL); + + return Status_OK; +} + + // Prints Grbl NGC parameters (coordinate offsets, probing, tool table) void report_ngc_parameters (void) { @@ -942,6 +972,10 @@ void report_build_info (char *line, bool extended) if(settings.mode == Mode_Lathe) strcat(buf, "LATHE,"); + #if NGC_EXPRESSIONS_ENABLE + strcat(buf, "EXPR,"); + #endif + #ifdef N_TOOLS if(hal.driver_cap.atc && hal.tool.change) strcat(buf, "ATC,"); @@ -1395,7 +1429,7 @@ static void report_settings_detail (settings_format_t format, const setting_deta switch(format) { case SettingsFormat_HumanReadable: - hal.stream.write("$"); + hal.stream.write(ASCII_EOL "$"); hal.stream.write(uitoa(setting->id + offset)); hal.stream.write(": "); if(setting->group == Group_Axis0) @@ -1454,6 +1488,26 @@ static void report_settings_detail (settings_format_t format, const setting_deta hal.stream.write(setting->max_value); } } +#ifndef NO_SETTINGS_DESCRIPTIONS + // Add description if driver is capable of outputting it... + if(hal.stream.write_n) { + const char *description = setting_get_description(setting->id); + if(description && *description != '\0') { + char *lf; + hal.stream.write(ASCII_EOL); + if((lf = strstr(description, "\\n"))) while(lf) { + hal.stream.write(ASCII_EOL); + hal.stream.write_n(description, lf - description); + description = lf + 2; + lf = strstr(description, "\\n"); + } + if(*description != '\0') { + hal.stream.write(ASCII_EOL); + hal.stream.write(description); + } + } + } +#endif break; case SettingsFormat_MachineReadable: diff --git a/report.h b/report.h index 7a6fc39..9fda85b 100644 --- a/report.h +++ b/report.h @@ -24,6 +24,7 @@ #define _REPORT_H_ #include "system.h" +#include "ngc_params.h" // Message types for uncoded messages typedef enum { @@ -59,7 +60,7 @@ message_code_t report_feedback_message (message_code_t message_code); void report_init_message (void); // Prints Grbl help. -status_code_t report_help (char *args, char *lcargs); +status_code_t report_help (char *args); void report_grbl_help(); // Prints Grbl settings @@ -80,6 +81,12 @@ void report_probe_parameters (void); // Prints current tool offsets. void report_tool_offsets (void); +// Prints NIST/LinuxCNC NGC parameter value +status_code_t report_ngc_parameter (ngc_param_id_t id); + +// Prints named LinuxCNC NGC parameter value +status_code_t report_named_ngc_parameter (char *arg); + // Prints Grbl NGC parameters (coordinate offsets, probe). void report_ngc_parameters (void); diff --git a/settings.c b/settings.c index 29d7671..78d19ec 100644 --- a/settings.c +++ b/settings.c @@ -77,6 +77,11 @@ PROGMEM const settings_t defaults = { .flags.legacy_rt_commands = DEFAULT_LEGACY_RTCOMMANDS, .flags.report_inches = DEFAULT_REPORT_INCHES, .flags.sleep_enable = DEFAULT_SLEEP_ENABLE, +#if DISABLE_G92_PERSISTENCE + .flags.g92_is_volatile = 1, +#else + .flags.g92_is_volatile = 0, +#endif #if DEFAULT_LASER_MODE .mode = Mode_Laser, .flags.disable_laser_during_hold = DEFAULT_ENABLE_LASER_DURING_HOLD, @@ -355,10 +360,14 @@ static status_code_t set_rotational_axes (setting_id_t id, uint_fast16_t int_val static status_code_t set_limits_invert_mask (setting_id_t id, uint_fast16_t int_value); #endif static status_code_t set_axis_setting (setting_id_t setting, float value); +#if COMPATIBILITY_LEVEL <= 1 +static status_code_t set_g92_disable_persistence (setting_id_t id, uint_fast16_t int_value); +#endif static float get_float (setting_id_t setting); static uint32_t get_int (setting_id_t id); static bool is_setting_available (const setting_detail_t *setting); + static char control_signals[] = "Reset,Feed hold,Cycle start,Safety door,Block delete,Optional stop,EStop,Probe connected,Motor fault"; static char spindle_signals[] = "Spindle enable,Spindle direction,PWM"; static char coolant_signals[] = "Flood,Mist"; @@ -481,6 +490,9 @@ PROGMEM static const setting_detail_t setting_detail[] = { { Setting_DualAxisLengthFailPercent, Group_Limits_DualAxis, "Dual axis length fail", "percent", Format_Decimal, "##0.0", "0", "100", Setting_IsExtended, &settings.homing.dual_axis.fail_length_percent, NULL, NULL }, { Setting_DualAxisLengthFailMin, Group_Limits_DualAxis, "Dual axis length fail min", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtended, &settings.homing.dual_axis.fail_distance_min, NULL, NULL }, { Setting_DualAxisLengthFailMax, Group_Limits_DualAxis, "Dual axis length fail max", "mm", Format_Decimal, "#####0.000", NULL, NULL, Setting_IsExtended, &settings.homing.dual_axis.fail_distance_max, NULL, NULL }, +#if COMPATIBILITY_LEVEL <= 1 + { Setting_DisableG92Persistence, Group_General, "Disable G92 persistence", NULL, Format_Bool, NULL, NULL, NULL, Setting_IsExtendedFn, set_g92_disable_persistence, get_int, NULL }, +#endif #if N_AXIS == 4 { Settings_Axis_Rotational, Group_Stepper, "Rotational axes", NULL, Format_Bitfield, "A-Axis", NULL, NULL, Setting_IsExtendedFn, set_rotational_axes, get_int, NULL } #elif N_AXIS == 5 @@ -612,7 +624,10 @@ PROGMEM static const setting_descr_t setting_descr[] = { { Setting_ToolChangePulloffRate, "Pull-off rate for the retract move before the slower locating phase." }, { Setting_DualAxisLengthFailPercent, "Dual axis length fail in percent of axis max travel." }, { Setting_DualAxisLengthFailMin, "Dual axis length fail minimum distance." }, - { Setting_DualAxisLengthFailMax, "Dual axis length fail minimum distance." } + { Setting_DualAxisLengthFailMax, "Dual axis length fail minimum distance." }, +#if COMPATIBILITY_LEVEL <= 1 + { Setting_DisableG92Persistence, "Disables save/restore of G92 offset to non-volatile storage (NVS)." }, +#endif }; #endif @@ -900,6 +915,15 @@ static status_code_t set_hold_actions (setting_id_t id, uint_fast16_t int_value) return Status_OK; } +#if COMPATIBILITY_LEVEL <= 1 +static status_code_t set_g92_disable_persistence (setting_id_t id, uint_fast16_t int_value) +{ + settings.flags.g92_is_volatile = int_value != 0; + + return Status_OK; +} +#endif + static status_code_t set_force_initialization_alarm (setting_id_t id, uint_fast16_t int_value) { settings.flags.force_initialization_alarm = int_value != 0; @@ -1231,6 +1255,10 @@ static uint32_t get_int (setting_id_t id) value = settings.tool_change.mode; break; + case Setting_DisableG92Persistence: + value = settings.flags.g92_is_volatile; + break; + #if N_AXIS > 3 case Settings_Axis_Rotational: value = (settings.steppers.is_rotational.mask & AXES_BITMASK) >> 3; @@ -1475,6 +1503,10 @@ bool read_global_settings () if(settings.mode == Mode_Laser && !hal.driver_cap.variable_spindle) settings.mode = Mode_Standard; +#if COMPATIBILITY_LEVEL > 1 && DISABLE_G92_PERSISTENCE + settings.flags.g92_is_volatile = On; +#endif + return ok && settings.version == SETTINGS_VERSION; } diff --git a/settings.h b/settings.h index 9a33c5a..67710fc 100644 --- a/settings.h +++ b/settings.h @@ -31,7 +31,7 @@ // Version of the persistent storage data. Will be used to migrate existing data from older versions of Grbl // when firmware is upgraded. Always stored in byte 0 of non-volatile storage -// TODO: add ftp port to network settings +// TODO: add ftp port to network settings, enable safety_door settings #if N_AXIS > 3 // TODO: remove on next version update #define SETTINGS_VERSION 20 // NOTE: Check settings_reset() when moving to next version. @@ -241,6 +241,7 @@ typedef enum { Setting_CoolantMaxTemp = 381, Setting_CoolantOffset = 382, Setting_CoolantGain = 383, + Setting_DisableG92Persistence = 384, Setting_EncoderSettingsBase = 400, // NOTE: Reserving settings values >= 400 for encoder settings. Up to 449. Setting_EncoderSettingsMax = 449, @@ -318,7 +319,8 @@ typedef union { legacy_rt_commands :1, restore_after_feed_hold :1, keep_coolant_state_on_door_open :1, - unassigned :7; + g92_is_volatile :1, + unassigned :6; }; } settingflags_t; @@ -354,6 +356,21 @@ typedef union { }; } reportmask_t; +typedef union { + uint8_t value; + struct { + uint8_t ignore_when_idle :1, + keep_coolant_on :1, + unassigned :6; + }; +} safety_door_setting_flags_t; + +typedef union { + safety_door_setting_flags_t flags; + float spindle_on_delay; + float coolant_on_delay; +} safety_door_settings_t; + typedef union { uint8_t value; struct { @@ -558,6 +575,7 @@ typedef struct { homing_settings_t homing; limit_settings_t limits; parking_settings_t parking; +// safety_door_settings_t safety_door; position_pid_t position; // Used for synchronized motion ioport_signals_t ioport; } settings_t; diff --git a/state_machine.c b/state_machine.c index 683a4d2..eac4fc0 100644 --- a/state_machine.c +++ b/state_machine.c @@ -24,7 +24,6 @@ */ #include -//#include #include "hal.h" #include "motion_control.h" @@ -41,7 +40,6 @@ static void state_await_toolchanged (uint_fast16_t rt_exec); static void state_await_waypoint_retract (uint_fast16_t rt_exec); static void state_restore (uint_fast16_t rt_exec); static void state_await_resumed (uint_fast16_t rt_exec); -static void state_await_restore (uint_fast16_t rt_exec); static void (* volatile stateHandler)(uint_fast16_t rt_exec) = state_idle; @@ -49,22 +47,33 @@ static float restore_spindle_rpm; static planner_cond_t restore_condition; static sys_state_t pending_state = STATE_IDLE, sys_state = STATE_IDLE; +typedef union { + uint8_t value; + struct { + uint8_t active :1, + motion :1, + restart :1, + restoring :1, + unassigned :4; + }; +} parking_flags_t; + typedef struct { float target[N_AXIS]; float restore_target[N_AXIS]; float retract_waypoint; - bool retracting; - bool restart_retract; - bool active; + volatile parking_flags_t flags; plan_line_data_t plan_data; } parking_data_t; // Declare and initialize parking local variables -static parking_data_t park; +static parking_data_t park = {0}; static void state_restore_conditions (planner_cond_t *condition, float rpm) { - if(!settings.parking.flags.enabled || !park.restart_retract) { + if (!settings.parking.flags.enabled || !park.flags.restart) { + + park.flags.restoring = On; // spindle_restore(condition->spindle, rpm); @@ -75,13 +84,15 @@ static void state_restore_conditions (planner_cond_t *condition, float rpm) delay_sec(SAFETY_DOOR_COOLANT_DELAY, DelayMode_SysSuspend); } + park.flags.restoring = Off; + sys.override.spindle_stop.value = 0; // Clear spindle stop override states } } bool initiate_hold (uint_fast16_t new_state) { - if(settings.parking.flags.enabled) { + if (settings.parking.flags.enabled) { memset(&park.plan_data, 0, sizeof(plan_line_data_t)); park.plan_data.condition.system_motion = On; park.plan_data.condition.no_feed_override = On; @@ -99,20 +110,20 @@ bool initiate_hold (uint_fast16_t new_state) restore_spindle_rpm = block->spindle.rpm; } - if(settings.mode == Mode_Laser && settings.flags.disable_laser_during_hold) + if (settings.mode == Mode_Laser && settings.flags.disable_laser_during_hold) enqueue_accessory_override(CMD_OVERRIDE_SPINDLE_STOP); - if(sys_state & (STATE_CYCLE|STATE_JOG)) { + if (sys_state & (STATE_CYCLE|STATE_JOG)) { st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. sys.step_control.execute_hold = On; // Initiate suspend state with active flag. stateHandler = state_await_hold; } - if(new_state == STATE_HOLD) + if (new_state == STATE_HOLD) sys.holding_state = Hold_Pending; else { sys.parking_state = Parking_Retracting; - park.active = false; + park.flags.value = 0; } sys.suspend = true; @@ -123,15 +134,15 @@ bool initiate_hold (uint_fast16_t new_state) bool state_door_reopened (void) { - return settings.parking.flags.enabled && park.restart_retract; + return settings.parking.flags.enabled && park.flags.restart; } void state_update (rt_exec_t rt_exec) { - if((rt_exec & EXEC_SAFETY_DOOR) && sys_state != STATE_SAFETY_DOOR) + if ((rt_exec & EXEC_SAFETY_DOOR) && sys_state != STATE_SAFETY_DOOR) state_set(STATE_SAFETY_DOOR); - else - stateHandler(rt_exec); + + stateHandler(rt_exec); } sys_state_t state_get (void) @@ -141,7 +152,7 @@ sys_state_t state_get (void) void state_set (sys_state_t new_state) { - if(new_state != sys_state) { + if (new_state != sys_state) { switch(new_state) { // Set up new state and handler @@ -151,20 +162,21 @@ void state_set (sys_state_t new_state) sys.parking_state = Parking_DoorClosed; sys.holding_state = Hold_NotHolding; sys_state = pending_state = new_state; + park.flags.value = 0; stateHandler = state_idle; break; case STATE_CYCLE: - if(sys_state == STATE_IDLE) { + if (sys_state == STATE_IDLE) { // Start cycle only if queued motions exist in planner buffer and the motion is not canceled. plan_block_t *block; if ((block = plan_get_current_block())) { sys_state = new_state; sys.steppers_deenergize = false; // Cancel stepper deenergize if pending. st_prep_buffer(); // Initialize step segment buffer before beginning cycle. - if(block->condition.spindle.synchronized) { + if (block->condition.spindle.synchronized) { - if(hal.spindle.reset_data) + if (hal.spindle.reset_data) hal.spindle.reset_data(); uint32_t index = hal.spindle.get_data(SpindleData_Counters)->index_count + 2; @@ -179,7 +191,7 @@ void state_set (sys_state_t new_state) break; case STATE_JOG: - if(sys_state == STATE_TOOL_CHANGE) + if (sys_state == STATE_TOOL_CHANGE) pending_state = STATE_TOOL_CHANGE; sys_state = new_state; stateHandler = state_cycle; @@ -191,10 +203,10 @@ void state_set (sys_state_t new_state) break; case STATE_HOLD: - if(sys.override.control.sync && sys.override.control.feed_hold_disable) + if (sys.override.control.sync && sys.override.control.feed_hold_disable) sys.flags.feed_hold_pending = On; - if(!((sys_state & STATE_JOG) || sys.override.control.feed_hold_disable)) { - if(!initiate_hold(new_state)) { + if (!((sys_state & STATE_JOG) || sys.override.control.feed_hold_disable)) { + if (!initiate_hold(new_state)) { sys.holding_state = Hold_Complete; stateHandler = state_await_resume; } @@ -204,14 +216,14 @@ void state_set (sys_state_t new_state) break; case STATE_SAFETY_DOOR: - if((sys_state & (STATE_ALARM|STATE_ESTOP|STATE_SLEEP|STATE_CHECK_MODE))) + if ((sys_state & (STATE_ALARM|STATE_ESTOP|STATE_SLEEP|STATE_CHECK_MODE))) return; grbl.report.feedback_message(Message_SafetyDoorAjar); // no break case STATE_SLEEP: sys.parking_state = Parking_Retracting; - if(!initiate_hold(new_state)) { - if(pending_state != new_state) { + if (!initiate_hold(new_state)) { + if (pending_state != new_state) { sys_state = new_state; state_await_hold(EXEC_CYCLE_COMPLETE); // "Simulate" a cycle stop } @@ -229,10 +241,10 @@ void state_set (sys_state_t new_state) break; } - if(!(sys_state & (STATE_ALARM|STATE_ESTOP))) + if (!(sys_state & (STATE_ALARM|STATE_ESTOP))) sys.alarm = Alarm_None; - if(grbl.on_state_change) + if (grbl.on_state_change) grbl.on_state_change(new_state); } } @@ -240,7 +252,7 @@ void state_set (sys_state_t new_state) // Suspend manager. Controls spindle overrides in hold states. void state_suspend_manager (void) { - if(stateHandler != state_await_resume || !gc_state.modal.spindle.on) + if (stateHandler != state_await_resume || !gc_state.modal.spindle.on) return; if (sys.override.spindle_stop.value) { @@ -269,12 +281,25 @@ void state_suspend_manager (void) } } +// ************** +// State handlers +// ************** + +/*! /brief No operation handler. + */ +static void state_noop (uint_fast16_t rt_exec) +{ + // Do nothing - state change requests are handled elsewhere or ignored. +} + +/*! /brief Waits for idle actions and executes them by switching to the appropriate sys_state. + */ static void state_idle (uint_fast16_t rt_exec) { - if((rt_exec & EXEC_CYCLE_START)) + if ((rt_exec & EXEC_CYCLE_START)) state_set(STATE_CYCLE); - if(rt_exec & EXEC_FEED_HOLD) + if (rt_exec & EXEC_FEED_HOLD) state_set(STATE_HOLD); if ((rt_exec & EXEC_TOOL_CHANGE)) { @@ -286,6 +311,8 @@ static void state_idle (uint_fast16_t rt_exec) state_set(STATE_SLEEP); } +/*! /brief Waits for cycle actions and executes them by switching to the appropriate sys_state. + */ static void state_cycle (uint_fast16_t rt_exec) { if (rt_exec == EXEC_CYCLE_START) @@ -308,11 +335,13 @@ static void state_cycle (uint_fast16_t rt_exec) state_set(STATE_HOLD); } +/*! /brief Waits for tool change cycle to end then restarts the cycle. + */ static void state_await_toolchanged (uint_fast16_t rt_exec) { if (rt_exec & EXEC_CYCLE_START) { - if(!gc_state.tool_change) { - if(hal.stream.suspend_read) + if (!gc_state.tool_change) { + if (hal.stream.suspend_read) hal.stream.suspend_read(false); // Tool change complete, restore "normal" stream input. sys.report.tool = On; } @@ -324,10 +353,12 @@ static void state_await_toolchanged (uint_fast16_t rt_exec) } } +/*! /brief Waits for motion to end to complete then executes actions depending on the current sys_state. + */ static void state_await_motion_cancel (uint_fast16_t rt_exec) { if (rt_exec & EXEC_CYCLE_COMPLETE) { - if(sys_state == STATE_JOG) { + if (sys_state == STATE_JOG) { sys.step_control.flags = 0; plan_reset(); st_reset(); @@ -338,11 +369,13 @@ static void state_await_motion_cancel (uint_fast16_t rt_exec) sys.suspend = false; } state_set(pending_state); - if(gc_state.tool_change) + if (gc_state.tool_change) state_set(STATE_TOOL_CHANGE); } } +/*! /brief Waits for feed hold to complete then executes actions depending on the current sys_state. + */ static void state_await_hold (uint_fast16_t rt_exec) { if (rt_exec & EXEC_CYCLE_COMPLETE) { @@ -352,7 +385,7 @@ static void state_await_hold (uint_fast16_t rt_exec) plan_cycle_reinitialize(); sys.step_control.flags = 0; - if(sys.alarm_pending) { + if (sys.alarm_pending) { system_set_exec_alarm(sys.alarm_pending); sys.alarm_pending = Alarm_None; } @@ -376,51 +409,61 @@ static void state_await_hold (uint_fast16_t rt_exec) // Parking requires parking axis homed, the current location not exceeding the??? // parking target location, and laser mode disabled. - if(settings.parking.flags.enabled && !sys.override.control.parking_disable && settings.mode != Mode_Laser) { + if (settings.parking.flags.enabled && !sys.override.control.parking_disable && settings.mode != Mode_Laser) { // Get current position and store as restore location. - if (!park.active) { - park.active = true; + if (!park.flags.active) { + park.flags.active = On; system_convert_array_steps_to_mpos(park.restore_target, sys.position); } - // Execute slow pull-out parking retract motion. - // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. + // Execute slow pull-out parking retract motion if parking axis is homed and parking target is above restore target. if (bit_istrue(sys.homed.mask, bit(settings.parking.axis)) && (park.restore_target[settings.parking.axis] < settings.parking.target)) { + bool await_motion; + handler_changed = true; stateHandler = state_await_waypoint_retract; - // Copy restore location to park target and calculate spindle retract waypoint. - memcpy(park.target, park.restore_target, sizeof(park.target)); - park.retract_waypoint = settings.parking.pullout_increment + park.target[settings.parking.axis]; - park.retract_waypoint = min(park.retract_waypoint, settings.parking.target); + // Copy current location to park target and calculate retract waypoint if not restarting. + if(park.flags.restart) + system_convert_array_steps_to_mpos(park.target, sys.position); + else { + memcpy(park.target, park.restore_target, sizeof(park.target)); + park.retract_waypoint = settings.parking.pullout_increment + park.target[settings.parking.axis]; + park.retract_waypoint = min(park.retract_waypoint, settings.parking.target); + } - // Retract spindle by pullout distance. Ensure retraction motion moves away from + // Retract by pullout distance. Ensure retraction motion moves away from // the workpiece and waypoint motion doesn't exceed the parking target location. - if (park.target[settings.parking.axis] < park.retract_waypoint) { + if ((await_motion = park.target[settings.parking.axis] < park.retract_waypoint)) { park.target[settings.parking.axis] = park.retract_waypoint; park.plan_data.feed_rate = settings.parking.pullout_rate; park.plan_data.condition.coolant = restore_condition.coolant; // Retain coolant state park.plan_data.condition.spindle = restore_condition.spindle; // Retain spindle state park.plan_data.spindle.rpm = restore_spindle_rpm; - if(!(park.retracting = mc_parking_motion(park.target, &park.plan_data))) - stateHandler(EXEC_CYCLE_COMPLETE); - } else - stateHandler(EXEC_CYCLE_COMPLETE); + await_motion = mc_parking_motion(park.target, &park.plan_data); + } + + if(!park.flags.restart) + park.flags.motion = await_motion; + + if (!await_motion) + stateHandler(EXEC_CYCLE_COMPLETE); // No motion, proceed to next step immediately. + } else { // Parking motion not possible. Just disable the spindle and coolant. // NOTE: Laser mode does not start a parking motion to ensure the laser stops immediately. hal.spindle.set_state((spindle_state_t){0}, 0.0f); // De-energize - if(!settings.flags.keep_coolant_state_on_door_open) + if (!settings.flags.keep_coolant_state_on_door_open) hal.coolant.set_state((coolant_state_t){0}); // De-energize - sys.parking_state = Parking_DoorAjar; + sys.parking_state = hal.control.get_state().safety_door_ajar ? Parking_DoorAjar : Parking_DoorClosed; } } else { hal.spindle.set_state((spindle_state_t){0}, 0.0f); // De-energize - if(!settings.flags.keep_coolant_state_on_door_open) + if (!settings.flags.keep_coolant_state_on_door_open) hal.coolant.set_state((coolant_state_t){0}); // De-energize - sys.parking_state = Parking_DoorAjar; + sys.parking_state = hal.control.get_state().safety_door_ajar ? Parking_DoorAjar : Parking_DoorClosed; } break; @@ -428,26 +471,34 @@ static void state_await_hold (uint_fast16_t rt_exec) break; } - if(!handler_changed) { + if (!handler_changed) { sys.holding_state = Hold_Complete; stateHandler = state_await_resume; } } } +/*! /brief Waits for action to execute when in feed hold state. + */ static void state_await_resume (uint_fast16_t rt_exec) { - if((rt_exec & EXEC_CYCLE_COMPLETE) && settings.parking.flags.enabled) { - if(sys.step_control.execute_sys_motion) + if ((rt_exec & EXEC_CYCLE_COMPLETE) && settings.parking.flags.enabled) { + if (sys.step_control.execute_sys_motion) { sys.step_control.execute_sys_motion = Off; - sys.parking_state = Parking_DoorAjar; + st_parking_restore_buffer(); // Restore step segment buffer to normal run state. + } + sys.parking_state = hal.control.get_state().safety_door_ajar ? Parking_DoorAjar : Parking_DoorClosed; } - if ((rt_exec & EXEC_CYCLE_START) && !(sys_state == STATE_SAFETY_DOOR && hal.control.get_state().safety_door_ajar)) { + if (rt_exec & EXEC_SLEEP) + state_set(STATE_SLEEP); - bool handler_changed = false; + if (rt_exec & EXEC_SAFETY_DOOR) + sys.parking_state = hal.control.get_state().safety_door_ajar ? Parking_DoorAjar : Parking_DoorClosed; - if(sys_state == STATE_HOLD && !sys.override.spindle_stop.value) + else if (rt_exec & EXEC_CYCLE_START) { + + if (sys_state == STATE_HOLD && !sys.override.spindle_stop.value) sys.override.spindle_stop.restore_cycle = On; switch (sys_state) { @@ -458,107 +509,102 @@ static void state_await_resume (uint_fast16_t rt_exec) case STATE_SLEEP: break; - // Resume door state when parking motion has retracted and door has been closed. case STATE_SAFETY_DOOR: + if (park.flags.restart || !hal.control.get_state().safety_door_ajar) { - if(settings.parking.flags.enabled) { - - park.restart_retract = false; + bool await_motion = false; + stateHandler = state_restore; sys.parking_state = Parking_Resuming; - // Execute fast restore motion to the pull-out position. Parking requires homing enabled. - // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. - if (park.retracting) { - handler_changed = true; - stateHandler = state_restore; + // Resume door state when parking motion has retracted and door has been closed. + if (park.flags.motion) { + + park.flags.restart = Off; + + // Execute fast restore motion to the pull-out position. // Check to ensure the motion doesn't move below pull-out position. - if (park.target[settings.parking.axis] <= settings.parking.target) { + if (park.restore_target[settings.parking.axis] <= settings.parking.target) { float target[N_AXIS]; - memcpy(target, park.target, sizeof(target)); + memcpy(target, park.restore_target, sizeof(target)); target[settings.parking.axis] = park.retract_waypoint; park.plan_data.feed_rate = settings.parking.rate; - if(!mc_parking_motion(target, &park.plan_data)) - stateHandler(EXEC_CYCLE_COMPLETE); - else - st_parking_setup_buffer(); - } else // tell next handler to proceed with final step immediately - stateHandler(EXEC_CYCLE_COMPLETE); + await_motion = mc_parking_motion(target, &park.plan_data); + } } - } else - // Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle. - // Block if safety door re-opened during prior restore actions. - state_restore_conditions(&restore_condition, restore_spindle_rpm); + + if (!await_motion) // No motion, proceed to next step immediately. + stateHandler(EXEC_CYCLE_COMPLETE); + } break; default: if (!settings.flags.restore_after_feed_hold) { - if(!hal.spindle.get_state().on) { + if (!hal.spindle.get_state().on) { gc_state.spindle.rpm = 0.0f; gc_state.modal.spindle.on = gc_state.modal.spindle.ccw = Off; } sys.override.spindle_stop.value = 0; // Clear spindle stop override states } else { - handler_changed = true; - stateHandler = state_await_restore; - stateHandler(0); + + if (restore_condition.spindle.on != hal.spindle.get_state().on) { + grbl.report.feedback_message(Message_SpindleRestore); + spindle_restore(restore_condition.spindle, restore_spindle_rpm); + } + + if (restore_condition.coolant.value != hal.coolant.get_state().value) { + // NOTE: Laser mode will honor this delay. An exhaust system is often controlled by coolant signals. + coolant_set_state(restore_condition.coolant); + delay_sec(SAFETY_DOOR_COOLANT_DELAY, DelayMode_SysSuspend); + } + + sys.override.spindle_stop.value = 0; // Clear spindle stop override states + + grbl.report.feedback_message(Message_None); } break; } - // Restart cycle if there is no further processing to take place - if(!(handler_changed || sys_state == STATE_SLEEP)) { + // Restart cycle + if (!(sys_state & (STATE_SLEEP|STATE_SAFETY_DOOR))) { state_set(STATE_IDLE); state_set(STATE_CYCLE); } - } - if (rt_exec & EXEC_SLEEP) - state_set(STATE_SLEEP); + } else if ((rt_exec & EXEC_DOOR_CLOSED) && !hal.control.get_state().safety_door_ajar) + sys.parking_state = Parking_DoorClosed; } -static void state_await_restore (uint_fast16_t rt_exec) +// ******************** +// Safety door handlers +// ******************** + +/*! /brief Waits until plunge motion abort is completed then calls state_await_hold() to restart retraction. +state_await_hold() is set to handle the cycle complete event. + */ +static void state_await_restart_retract (uint_fast16_t rt_exec) { - static bool restart = false; + if (rt_exec & EXEC_CYCLE_COMPLETE) { - if(rt_exec == 0) { - - restart = true; - - if (restore_condition.spindle.on != hal.spindle.get_state().on) { - grbl.report.feedback_message(Message_SpindleRestore); - spindle_restore(restore_condition.spindle, restore_spindle_rpm); + if (sys.step_control.execute_sys_motion) { + sys.step_control.execute_sys_motion = Off; + st_parking_restore_buffer(); // Restore step segment buffer to normal run state. } - if (restore_condition.coolant.value != hal.coolant.get_state().value) { - // NOTE: Laser mode will honor this delay. An exhaust system is often controlled by this pin. - coolant_set_state(restore_condition.coolant); - delay_sec(SAFETY_DOOR_COOLANT_DELAY, DelayMode_SysSuspend); - } - - sys.override.spindle_stop.value = 0; // Clear spindle stop override states - - grbl.report.feedback_message(Message_None); - - if(restart) { - state_set(STATE_IDLE); - state_set(STATE_CYCLE); - } + stateHandler = state_await_hold; + stateHandler(EXEC_CYCLE_COMPLETE); } - - if(rt_exec & EXEC_FEED_HOLD) { - restart = false; - stateHandler = state_await_resume; - } - } +/*! /brief Sets up a feed hold to abort plunge motion. +state_await_restart_retract() is set to handle the cycle complete event. + */ static void restart_retract (void) { grbl.report.feedback_message(Message_SafetyDoorAjar); - stateHandler = state_await_hold; + stateHandler = state_await_restart_retract; - park.restart_retract = true; + park.flags.restart = On; sys.parking_state = Parking_Retracting; if (sys.step_control.execute_sys_motion) { @@ -569,31 +615,26 @@ static void restart_retract (void) stateHandler(EXEC_CYCLE_COMPLETE); } -static void state_await_waypoint_cancel (uint_fast16_t rt_exec) -{ - if (rt_exec & EXEC_SAFETY_DOOR) - restart_retract(); - - else if (rt_exec & EXEC_CYCLE_COMPLETE) { - sys.parking_state = Parking_Cancel; - sys.step_control.execute_hold = Off; - state_restore(rt_exec); - } -} - +/*! /brief Waits until slow plunge motion is completed then deenergize spindle and coolant and execute fast retract motion. +state_await_resume() is set to handle the cycle complete event. + */ static void state_await_waypoint_retract (uint_fast16_t rt_exec) { if (rt_exec & EXEC_CYCLE_COMPLETE) { - if(sys.step_control.execute_sys_motion) + bool await_motion = false; + + if (sys.step_control.execute_sys_motion) { sys.step_control.execute_sys_motion = Off; + st_parking_restore_buffer(); // Restore step segment buffer to normal run state. + } // NOTE: Clear accessory state after retract and after an aborted restore motion. park.plan_data.condition.spindle.value = 0; park.plan_data.spindle.rpm = 0.0f; hal.spindle.set_state(park.plan_data.condition.spindle, 0.0f); // De-energize - if(!settings.flags.keep_coolant_state_on_door_open) { + if (!settings.flags.keep_coolant_state_on_door_open) { park.plan_data.condition.coolant.value = 0; hal.coolant.set_state(park.plan_data.condition.coolant); // De-energize } @@ -601,72 +642,87 @@ static void state_await_waypoint_retract (uint_fast16_t rt_exec) stateHandler = state_await_resume; // Execute fast parking retract motion to parking target location. - if (park.target[settings.parking.axis] < settings.parking.target) { + if (park.flags.motion && park.target[settings.parking.axis] < settings.parking.target) { float target[N_AXIS]; memcpy(target, park.target, sizeof(target)); target[settings.parking.axis] = settings.parking.target; park.plan_data.feed_rate = settings.parking.rate; - if(mc_parking_motion(target, &park.plan_data)) - park.retracting = true; - else - stateHandler(EXEC_CYCLE_COMPLETE); - } else - stateHandler(EXEC_CYCLE_COMPLETE); + await_motion = mc_parking_motion(target, &park.plan_data); + } - } else if (rt_exec & EXEC_CYCLE_START) { - - stateHandler = state_await_waypoint_cancel; - - if (sys.step_control.execute_sys_motion) { - st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. - sys.step_control.execute_hold = On; - sys.step_control.execute_sys_motion = On; - } else // else NO_MOTION is active. + if (!await_motion) stateHandler(EXEC_CYCLE_COMPLETE); } } +/*! /brief Waits until fast plunge motion is completed then restore spindle and coolant and execute slow plunge motion. +state_await_resumed() is set to handle the cycle complete event. +Note: A safety door event during restoration or motion will halt it and restart the retract sequence. + */ static void state_restore (uint_fast16_t rt_exec) { - if (rt_exec & EXEC_SAFETY_DOOR) - restart_retract(); + if (rt_exec & EXEC_SAFETY_DOOR) { + if(park.flags.restoring) + park.flags.restart = On; + else + restart_retract(); + } else if (rt_exec & EXEC_CYCLE_COMPLETE) { - if(sys.step_control.execute_sys_motion) - sys.step_control.execute_sys_motion = Off; + bool await_motion = false; + if (sys.step_control.execute_sys_motion) { + sys.step_control.execute_sys_motion = Off; + st_parking_restore_buffer(); // Restore step segment buffer to normal run state. + } + + park.flags.restart = Off; stateHandler = state_await_resumed; - // Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle. - // Block if safety door re-opened during prior restore actions. - if(sys.parking_state != Parking_Cancel) - state_restore_conditions(&restore_condition, restore_spindle_rpm); + // Restart spindle and coolant, delay to power-up. + state_restore_conditions(&restore_condition, restore_spindle_rpm); - park.restart_retract = false; - sys.parking_state = Parking_Resuming; + if(park.flags.restart) { + // Restart flag was set by a safety door event during + // conditions restore so restart retract. + restart_retract(); + return; + } - // Execute slow plunge motion from pull-out position to resume position. + if (park.flags.motion) { - // Regardless if the retract parking motion was a valid/safe motion or not, the - // restore parking motion should logically be valid, either by returning to the - // original position through valid machine space or by not moving at all. - park.plan_data.feed_rate = settings.parking.pullout_rate; - park.plan_data.condition.coolant = restore_condition.coolant; - park.plan_data.condition.spindle = restore_condition.spindle; - park.plan_data.spindle.rpm = restore_spindle_rpm; - if(!mc_parking_motion(park.restore_target, &park.plan_data)) - stateHandler(EXEC_CYCLE_COMPLETE); // No motion, proceed to next step + sys.parking_state = Parking_Resuming; + + // Execute slow plunge motion from pull-out position to resume position. + + // Regardless if the retract parking motion was a valid/safe motion or not, the + // restore parking motion should logically be valid, either by returning to the + // original position through valid machine space or by not moving at all. + park.plan_data.feed_rate = settings.parking.pullout_rate; + park.plan_data.condition.coolant = restore_condition.coolant; + park.plan_data.condition.spindle = restore_condition.spindle; + park.plan_data.spindle.rpm = restore_spindle_rpm; + await_motion = mc_parking_motion(park.restore_target, &park.plan_data); + } + + if (!await_motion) + stateHandler(EXEC_CYCLE_COMPLETE); // No motion, proceed to next step immediately. } } +/*! /brief Waits until slow plunge motion is complete then restart the cycle. +Note: A safety door event during the motion will halt it and restart the retract sequence. + */ static void state_await_resumed (uint_fast16_t rt_exec) { if (rt_exec & EXEC_SAFETY_DOOR) restart_retract(); else if (rt_exec & EXEC_CYCLE_COMPLETE) { - if(sys.step_control.execute_sys_motion) { + sys.parking_state = Parking_DoorClosed; + park.flags.value = 0; + if (sys.step_control.execute_sys_motion) { sys.step_control.flags = 0; st_parking_restore_buffer(); // Restore step segment buffer to normal run state. } @@ -674,8 +730,3 @@ static void state_await_resumed (uint_fast16_t rt_exec) state_set(STATE_CYCLE); } } - -static void state_noop (uint_fast16_t rt_exec) -{ - // Do nothing - state change requests are handled elsewhere or ignored. -} diff --git a/system.c b/system.c index c11db1c..d5a8199 100644 --- a/system.c +++ b/system.c @@ -145,7 +145,7 @@ void system_execute_startup (void) if (!settings_read_startup_line(n, line)) report_execute_startup_message(line, Status_SettingReadFail); else if (*line != '\0') - report_execute_startup_message(line, gc_execute_block(line, NULL)); + report_execute_startup_message(line, gc_execute_block(line)); } } } @@ -177,7 +177,7 @@ status_code_t read_int (char *s, int32_t *value) PROGMEM static const sys_command_t sys_commands[] = { { "G", true, output_parser_state }, { "J", false, jog }, - { "#", true, output_ngc_parameters }, + { "#", false, output_ngc_parameters }, { "$", false, output_settings }, { "+", false, output_all_settings }, #ifndef NO_SETTINGS_DESCRIPTIONS @@ -231,6 +231,57 @@ PROGMEM static const sys_command_t sys_commands[] = { #endif }; +void system_command_help (void) +{ + hal.stream.write("$I - output system information" ASCII_EOL); + hal.stream.write("$ - output setting value" ASCII_EOL); + hal.stream.write("$= - assign to settings " ASCII_EOL); + hal.stream.write("$I - output system information" ASCII_EOL); + hal.stream.write("$I+ - output extended system information" ASCII_EOL); + hal.stream.write("$$ - output all setting values" ASCII_EOL); + hal.stream.write("$+ - output all setting values" ASCII_EOL); + hal.stream.write("$$= - output setting details for setting " ASCII_EOL); + hal.stream.write("$# - output offsets, tool table, probing and home position" ASCII_EOL); + hal.stream.write("$#= - output value for parameter " ASCII_EOL); + hal.stream.write("$G - output parser state" ASCII_EOL); + hal.stream.write("$N - output startup lines" ASCII_EOL); + if(settings.homing.flags.enabled) + hal.stream.write("$H - home configured axes" ASCII_EOL); + if(settings.homing.flags.single_axis_commands) + hal.stream.write("$H - home single axis" ASCII_EOL); + hal.stream.write("$X - unlock machine" ASCII_EOL); + hal.stream.write("$SLP - enter sleep mode" ASCII_EOL); + hal.stream.write("$HELP - output help topics" ASCII_EOL); + hal.stream.write("$HELP - output help for " ASCII_EOL); + hal.stream.write("$RST=* - restore/reset all" ASCII_EOL); + hal.stream.write("$RST=$ - restore default settings" ASCII_EOL); + if(settings_get_details()->on_get_settings) + hal.stream.write("$RST=& - restore driver and plugin default settings" ASCII_EOL); +#ifdef N_TOOLS + hal.stream.write("$RST=# - reset offsets and tool data" ASCII_EOL); +#else + hal.stream.write("$RST=# - reset offsets" ASCII_EOL); +#endif + hal.stream.write("$TLR - set tool offset reference" ASCII_EOL); + hal.stream.write("$TPW - probe tool plate" ASCII_EOL); + hal.stream.write("$EA - enumerate alarms" ASCII_EOL); + hal.stream.write("$EAG - enumerate alarms, Grbl formatted" ASCII_EOL); + hal.stream.write("$EE - enumerate status codes" ASCII_EOL); + hal.stream.write("$EEG - enumerate status codes, Grbl formatted" ASCII_EOL); + hal.stream.write("$ES - enumerate settings" ASCII_EOL); + hal.stream.write("$ESG - enumerate settings, Grbl formatted" ASCII_EOL); + hal.stream.write("$ESH- enumerate settings, grblHAL formatted" ASCII_EOL); + hal.stream.write("$ESG - enumerate alarms" ASCII_EOL); + hal.stream.write("$E* - enumerate alarms, status codes and settings" ASCII_EOL); + if(hal.enumerate_pins) + hal.stream.write("$PINS - enumerate pin bindings" ASCII_EOL); + hal.stream.write("$LEV - output last control signal events" ASCII_EOL); + hal.stream.write("$LIM - output current limit pins state" ASCII_EOL); +#ifndef NO_SETTINGS_DESCRIPTIONS + hal.stream.write("$SED= - output settings description for setting " ASCII_EOL); +#endif +} + // Directs and executes one line of formatted input from protocol_process. While mostly // incoming streaming g-code blocks, this also executes Grbl internal commands, such as // settings, initiating the homing cycle, and toggling switch states. This differs from @@ -248,9 +299,6 @@ status_code_t system_execute_line (char *line) return Status_OK; } - if(strlen(line) >= ((LINE_BUFFER_SIZE / 2) - 1)) - return Status_Overflow; - sys_commands_t base = { .n_commands = sizeof(sys_commands) / sizeof(sys_command_t), .commands = sys_commands, @@ -258,37 +306,38 @@ status_code_t system_execute_line (char *line) }; status_code_t retval = Status_Unhandled; - char c, *org = line, *ucline = line, *lcline = line + (LINE_BUFFER_SIZE / 2); - // Uppercase original and copy original out in the buffer - // TODO: create a common function for stripping down uppercase version? - do { - c = *org++; - if(c != ' ') // Remove spaces from uppercase version - *ucline++ = CAPS(c); - *lcline++ = c; - } while(c); + char c, *s1, *s2; - lcline = line + (LINE_BUFFER_SIZE / 2); + s1 = s2 = ++line; - if(!strncmp(&line[1], "HELP", 4)) - return report_help(&line[5], &lcline[5]); - - char *args = strchr(line, '='), *lcargs = strchr(lcline, '='); - - if(args) { - *args++ = '\0'; - *lcargs++ = '\0'; + c = *s1; + while(c && c != '=') { + if(c != ' ') + *s2++ = CAPS(c); + c = *++s1; } + while((c = *s1++)) + *s2++ = c; + + *s2 = '\0'; + + if(!strncmp(line, "HELP", 4)) + return report_help(&line[4]); + + char *args = strchr(line, '='); + + if(args) + *args++ = '\0'; + uint_fast8_t idx; sys_commands_t *cmd = &base; - do { for(idx = 0; idx < cmd->n_commands; idx++) { - if(!strcmp(&line[1], cmd->commands[idx].command)) { - if(!cmd->commands[idx].noargs || lcargs == NULL) { - if((retval = cmd->commands[idx].execute(state_get(), lcargs)) != Status_Unhandled) + if(!strcmp(line, cmd->commands[idx].command)) { + if(!cmd->commands[idx].noargs || args == NULL) { + if((retval = cmd->commands[idx].execute(state_get(), args)) != Status_Unhandled) break; } } @@ -298,26 +347,26 @@ status_code_t system_execute_line (char *line) // Let user code have a peek at system commands before check for global setting if(retval == Status_Unhandled && grbl.on_unknown_sys_command) { - if(lcargs) - *(--lcargs) = '='; + if(args) + *(--args) = '='; - retval = grbl.on_unknown_sys_command(state_get(), lcline); + retval = grbl.on_unknown_sys_command(state_get(), line); - if(lcargs) - *lcargs++ = '\0'; + if(args) + *args++ = '\0'; } if (retval == Status_Unhandled) { // Check for global setting, store if so if(state_get() == STATE_IDLE || (state_get() & (STATE_ALARM|STATE_ESTOP|STATE_CHECK_MODE))) { - uint_fast8_t counter = 1; + uint_fast8_t counter = 0; float parameter; if(!read_float(line, &counter, ¶meter)) retval = Status_BadNumberFormat; else if(!isintf(parameter)) retval = Status_InvalidStatement; else if(args) - retval = settings_store_setting((setting_id_t)parameter, lcargs); + retval = settings_store_setting((setting_id_t)parameter, args); else retval = report_grbl_setting((setting_id_t)parameter, NULL); } else @@ -339,7 +388,7 @@ static status_code_t jog (sys_state_t state, char *args) args -= 2; } - return args == NULL ? Status_InvalidStatement : gc_execute_block(strcaps(args), NULL); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions. + return args == NULL ? Status_InvalidStatement : gc_execute_block(args); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions. } static status_code_t enumerate_alarms (sys_state_t state, char *args) @@ -536,7 +585,7 @@ static status_code_t disable_lock (sys_state_t state, char *args) static status_code_t output_help (sys_state_t state, char *args) { - return report_help(args, args); + return report_help(args); } static status_code_t go_home (sys_state_t state, axes_signals_t axes) @@ -667,9 +716,19 @@ static status_code_t tool_probe_workpiece (sys_state_t state, char *args) static status_code_t output_ngc_parameters (sys_state_t state, char *args) { - report_ngc_parameters(); + status_code_t retval = Status_OK; - return Status_OK; + if(args) { + int32_t id; + retval = read_int(args, &id); + if(retval == Status_OK && id >= 0) + retval = report_ngc_parameter((ngc_param_id_t)id); + else + retval = report_named_ngc_parameter(args); + } else + report_ngc_parameters(); + + return retval; } static status_code_t build_info (sys_state_t state, char *args) @@ -781,11 +840,11 @@ static status_code_t set_startup_line (sys_state_t state, char *args, uint_fast8 status_code_t retval = Status_OK; - strcaps(args); + args = gc_normalize_block(args, NULL); if(strlen(args) >= (sizeof(stored_line_t) - 1)) retval = Status_Overflow; - else if ((retval = gc_execute_block(args, NULL)) == Status_OK) // Execute gcode block to ensure block is valid. + else if ((retval = gc_execute_block(args)) == Status_OK) // Execute gcode block to ensure block is valid. settings_write_startup_line(lnr, args); return retval; diff --git a/system.h b/system.h index 3fc2173..8e46415 100644 --- a/system.h +++ b/system.h @@ -53,6 +53,7 @@ know when there is a realtime command to execute. #define EXEC_GCODE_REPORT bit(11) #define EXEC_TLO_REPORT bit(12) #define EXEC_RT_COMMAND bit(13) +#define EXEC_DOOR_CLOSED bit(14) ///@} //! \def sys_state @@ -295,6 +296,9 @@ void system_apply_jog_limits (float *target); //! Raise and report alarm state void system_raise_alarm (alarm_code_t alarm); +//! Provide system command help +void system_command_help (void); + // Special handlers for setting and clearing Grbl's real-time execution flags. #define system_set_exec_state_flag(mask) hal.set_bits_atomic(&sys.rt_exec_state, (mask)) #define system_clear_exec_state_flag(mask) hal.clear_bits_atomic(&sys.rt_exec_state, (mask))