From 532c359a11c71827895085e9355ba01dbad439fb Mon Sep 17 00:00:00 2001 From: Sonny Jeon Date: Sun, 25 May 2014 16:05:28 -0600 Subject: [PATCH] Major g-code parser overhaul. 100%* compliant. Other related updates. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Completely overhauled the g-code parser. It’s now 100%* compliant. (* may have some bugs). Being compliant, here are some of the major differences. - SMALLER and JUST AS FAST! A number of optimizations were found that sped things up and allowed for the more thorough error-checking to be installed without a speed hit. Trimmed a lot of ‘fat’ in the parser and still was able to make it significantly smaller than it was. - No default feed rate setting! Removed completely! This doesn’t exist in the g-code standard. So, it now errors out whenever it’s undefined for motions that require it (G1/2/3/38.2). - Any g-code parser error expunges the ENTIRE block. This means all information is lost and not passed on to the running state. Before some of the states would remain, which could have led to some problems. - If the g-code block passes all of the error-checks, the g-code state is updated and all motions are executed according to the order of execution. - Changes in spindle speed, when already running, will update the output pin accordingly. This fixes a bug, where it wouldn’t update the speed. - Update g-code parser error reporting. Errors now return detailed information of what exact went wrong. The most common errors return a short text description. For less common errors, the parser reports ‘Invalid gcode ID:20’, where 20 is a error ID. A list of error code IDs and their descriptions will be documented for user reference elsewhere to save flash space. - Other notable changes: - Added a print integer routine for uint8 variables. This saved significant flash space by switching from a heavier universal print integer routine. - Saved some flash space with our own short hypotenuse calculation - Some arc computation flash and memory optimizations. --- config.h | 2 +- coolant_control.c | 3 + coolant_control.h | 5 - defaults.h | 17 +- gcode.c | 1307 +++++++++++++++++++++++++++------------------ gcode.h | 169 ++++-- motion_control.c | 45 +- motion_control.h | 8 +- nuts_bolts.c | 8 +- nuts_bolts.h | 6 +- print.c | 36 +- print.h | 2 + probe.c | 8 +- probe.h | 3 + protocol.c | 45 +- report.c | 124 ++--- report.h | 44 +- settings.c | 40 +- settings.h | 7 +- spindle_control.c | 3 + spindle_control.h | 5 - system.c | 16 +- 22 files changed, 1162 insertions(+), 741 deletions(-) diff --git a/config.h b/config.h index 55114bf..ccba270 100644 --- a/config.h +++ b/config.h @@ -29,7 +29,7 @@ #define config_h // Default settings. Used when resetting EEPROM. Change to desired name in defaults.h -#define DEFAULTS_SHERLINE_5400 +#define DEFAULTS_GENERIC // Serial baud rate #define BAUD_RATE 115200 diff --git a/coolant_control.c b/coolant_control.c index a3f2554..9d66c50 100644 --- a/coolant_control.c +++ b/coolant_control.c @@ -21,6 +21,7 @@ #include "system.h" #include "coolant_control.h" #include "protocol.h" +#include "gcode.h" void coolant_init() @@ -44,6 +45,8 @@ void coolant_stop() void coolant_run(uint8_t mode) { + if (sys.state != STATE_CHECK_MODE) { return; } + protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program. if (mode == COOLANT_FLOOD_ENABLE) { COOLANT_FLOOD_PORT |= (1 << COOLANT_FLOOD_BIT); diff --git a/coolant_control.h b/coolant_control.h index c19a673..83ce30b 100644 --- a/coolant_control.h +++ b/coolant_control.h @@ -22,11 +22,6 @@ #define coolant_control_h -#define COOLANT_MIST_ENABLE 2 -#define COOLANT_FLOOD_ENABLE 1 -#define COOLANT_DISABLE 0 // Must be zero. - - void coolant_init(); void coolant_stop(); void coolant_run(uint8_t mode); diff --git a/defaults.h b/defaults.h index 9699eba..f2726a7 100644 --- a/defaults.h +++ b/defaults.h @@ -42,7 +42,6 @@ #define DEFAULT_Y_MAX_TRAVEL 200.0 // mm #define DEFAULT_Z_MAX_TRAVEL 200.0 // mm #define DEFAULT_STEP_PULSE_MICROSECONDS 10 - #define DEFAULT_FEEDRATE 250.0 // mm/min #define DEFAULT_STEPPING_INVERT_MASK 0 #define DEFAULT_DIRECTION_INVERT_MASK ((1<. */ -/* This code is inspired by the Arduino GCode Interpreter by Mike Ellery and the NIST RS274/NGC Interpreter - by Kramer, Proctor and Messina. */ - #include "system.h" #include "settings.h" #include "protocol.h" @@ -29,34 +26,28 @@ #include "motion_control.h" #include "spindle_control.h" #include "coolant_control.h" +#include "probe.h" #include "report.h" +#define MAX_LINE_NUMBER 99999 + +#define AXIS_COMMAND_NONE 0 +#define AXIS_COMMAND_NON_MODAL 1 +#define AXIS_COMMAND_MOTION_MODE 2 + // Declare gc extern struct -parser_state_t gc; +parser_state_t gc_state; +parser_block_t gc_block; -#define FAIL(status) gc.status_code = status; - -static uint8_t next_statement(char *letter, float *float_ptr, char *line, uint8_t *char_counter); -static void gc_convert_arc_radius_mode(float *target) __attribute__((noinline)); - - -static void select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2) -{ - gc.plane_axis_0 = axis_0; - gc.plane_axis_1 = axis_1; - gc.plane_axis_2 = axis_2; -} +#define FAIL(status) return(status); void gc_init() { - memset(&gc, 0, sizeof(gc)); - gc.feed_rate = settings.default_feed_rate; - select_plane(X_AXIS, Y_AXIS, Z_AXIS); - gc.absolute_mode = true; + memset(&gc_state, 0, sizeof(gc_state)); // Load default G54 coordinate system. - if (!(settings_read_coord_data(gc.coord_select,gc.coord_system))) { + if (!(settings_read_coord_data(gc_state.modal.coord_select,gc_state.coord_system))) { report_status_message(STATUS_SETTING_READ_FAIL); } } @@ -68,16 +59,19 @@ void gc_sync_position() { uint8_t i; for (i=0; i 255, variable type must be changed to uint16_t. - float inverse_feed_rate = -1; // negative inverse_feed_rate means no inverse_feed_rate specified - uint8_t absolute_override = false; // true(1) = absolute motion for this block only {G53} - uint8_t non_modal_action = NON_MODAL_NONE; // Tracks the actions of modal group 0 (non-modal) - - float target[N_AXIS]; - clear_vector(target); // XYZ(ABC) axes parameters. + while (line[char_counter] != 0) { // Loop until no more g-code words in line. + + // Import the next g-code word, expecting a letter followed by a value. Otherwise, error out. + letter = line[char_counter]; + if((letter < 'A') || (letter > 'Z')) { FAIL(STATUS_EXPECTED_COMMAND_LETTER); } // [Expected word letter] + char_counter++; + if (!read_float(line, &char_counter, &value)) { FAIL(STATUS_BAD_NUMBER_FORMAT); } // [Expected word value] - #ifdef USE_LINE_NUMBERS - int32_t line_number = 0; - #endif - gc.arc_radius = 0; - clear_vector(gc.arc_offset); // IJK Arc offsets are incremental. Value of zero indicates no change. - - gc.status_code = STATUS_OK; - - /* Pass 1: Commands and set all modes. Check for modal group violations. - NOTE: Modal group numbers are defined in Table 4 of NIST RS274-NGC v3, pg.20 */ - uint8_t group_number = MODAL_GROUP_NONE; - while(next_statement(&letter, &value, line, &char_counter)) { + // Convert values to smaller uint8 significand and mantissa values for parsing this word. + // NOTE: Mantissa is multiplied by 1000 to catch non-integer command values. int_value = trunc(value); + mantissa = trunc(1000*(value - int_value)); // Compute mantissa for Gxx.x commands + + // Check if the g-code word is supported or errors due to modal group violations or has + // been repeated in the g-code block. If ok, update the command or record its value. switch(letter) { + + /* 'G' and 'M' Command Words: Parse commands and check for modal group violations. + NOTE: Modal group numbers are defined in Table 4 of NIST RS274-NGC v3, pg.20 */ + case 'G': - // Set modal group values + // Determine 'G' command and its modal group switch(int_value) { - case 4: case 10: case 28: case 30: case 53: case 92: group_number = MODAL_GROUP_0; break; - case 0: case 1: case 2: case 3: case 38: case 80: group_number = MODAL_GROUP_1; break; - case 17: case 18: case 19: group_number = MODAL_GROUP_2; break; - case 90: case 91: group_number = MODAL_GROUP_3; break; - case 93: case 94: group_number = MODAL_GROUP_5; break; - case 20: case 21: group_number = MODAL_GROUP_6; break; - case 54: case 55: case 56: case 57: case 58: case 59: group_number = MODAL_GROUP_12; break; - } - // Set 'G' commands - switch(int_value) { - case 0: gc.motion_mode = MOTION_MODE_SEEK; break; - case 1: gc.motion_mode = MOTION_MODE_LINEAR; break; - case 2: gc.motion_mode = MOTION_MODE_CW_ARC; break; - case 3: gc.motion_mode = MOTION_MODE_CCW_ARC; break; - case 4: non_modal_action = NON_MODAL_DWELL; break; - case 10: non_modal_action = NON_MODAL_SET_COORDINATE_DATA; break; - case 17: select_plane(X_AXIS, Y_AXIS, Z_AXIS); break; - case 18: select_plane(Z_AXIS, X_AXIS, Y_AXIS); break; - case 19: select_plane(Y_AXIS, Z_AXIS, X_AXIS); break; - case 20: gc.inches_mode = true; break; - case 21: gc.inches_mode = false; break; - case 28: case 30: - int_value = trunc(10*value); // Multiply by 10 to pick up Gxx.1 + case 10: case 28: case 30: case 92: + // Check for G10/28/30/92 being called with G0/1/2/3/38 on same block. + if (mantissa == 0) { // Ignore G28.1, G30.1, and G92.1 + if (axis_explicit) { FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); } // [Axis word/command conflict] + axis_explicit = AXIS_COMMAND_NON_MODAL; + } + // No break. Continues to next line. + case 4: case 53: + word_bit = MODAL_GROUP_G0; switch(int_value) { - case 280: non_modal_action = NON_MODAL_GO_HOME_0; break; - case 281: non_modal_action = NON_MODAL_SET_HOME_0; break; - case 300: non_modal_action = NON_MODAL_GO_HOME_1; break; - case 301: non_modal_action = NON_MODAL_SET_HOME_1; break; - default: FAIL(STATUS_UNSUPPORTED_STATEMENT); + case 4: gc_block.non_modal_command = NON_MODAL_DWELL; break; // G4 + case 10: gc_block.non_modal_command = NON_MODAL_SET_COORDINATE_DATA; break; // G10 + case 28: + switch(mantissa) { + case 0: gc_block.non_modal_command = NON_MODAL_GO_HOME_0; break; // G28 + case 100: gc_block.non_modal_command = NON_MODAL_SET_HOME_0; break; // G28.1 + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G28.x command] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + break; + case 30: + switch(mantissa) { + case 0: gc_block.non_modal_command = NON_MODAL_GO_HOME_1; break; // G30 + case 100: gc_block.non_modal_command = NON_MODAL_SET_HOME_1; break; // G30.1 + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G30.x command] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + break; + case 53: gc_block.non_modal_command = NON_MODAL_ABSOLUTE_OVERRIDE; break; // G53 + case 92: + switch(mantissa) { + case 0: gc_block.non_modal_command = NON_MODAL_SET_COORDINATE_OFFSET; break; // G92 + case 100: gc_block.non_modal_command = NON_MODAL_RESET_COORDINATE_OFFSET; break; // G92.1 + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G92.x command] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + break; } break; - case 38: - int_value = trunc(10*value); // Multiply by 10 to pick up Gxx.1 + case 0: case 1: case 2: case 3: case 38: + // Check for G0/1/2/3/38 being called with G10/28/30/92 on same block. + if (axis_explicit) { FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); } // [Axis word/command conflict] + axis_explicit = AXIS_COMMAND_MOTION_MODE; + // No break. Continues to next line. + case 80: + word_bit = MODAL_GROUP_G1; switch(int_value) { - case 382: gc.motion_mode = MOTION_MODE_PROBE; break; - // case 383: gc.motion_mode = MOTION_MODE_PROBE_NO_ERROR; break; // Not supported. - // case 384: // Not supported. - // case 385: // Not supported. - default: FAIL(STATUS_UNSUPPORTED_STATEMENT); - } - break; - case 53: absolute_override = true; break; - case 54: case 55: case 56: case 57: case 58: case 59: - gc.coord_select = int_value-54; + case 0: gc_block.modal.motion = MOTION_MODE_SEEK; break; // G0 + case 1: gc_block.modal.motion = MOTION_MODE_LINEAR; break; // G1 + case 2: gc_block.modal.motion = MOTION_MODE_CW_ARC; break; // G2 + case 3: gc_block.modal.motion = MOTION_MODE_CCW_ARC; break; // G3 + case 38: + switch(mantissa) { + case 200: gc_block.modal.motion = MOTION_MODE_PROBE; break; // G38.2 + // NOTE: If G38.3+ are enabled, change mantissa variable type to uint16_t. + // case 300: gc_block.modal.motion = MOTION_MODE_PROBE_NO_ERROR; break; // G38.3 Not supported. + // case 400: // Not supported. + // case 500: // Not supported. + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G38.x command] + } + mantissa = 0; // Set to zero to indicate valid non-integer G command. + break; + case 80: gc_block.modal.motion = MOTION_MODE_NONE; break; // G80 + } break; - case 80: gc.motion_mode = MOTION_MODE_CANCEL; break; - case 90: gc.absolute_mode = true; break; - case 91: gc.absolute_mode = false; break; - case 92: - int_value = trunc(10*value); // Multiply by 10 to pick up G92.1 + case 17: case 18: case 19: + word_bit = MODAL_GROUP_G2; switch(int_value) { - case 920: non_modal_action = NON_MODAL_SET_COORDINATE_OFFSET; break; - case 921: non_modal_action = NON_MODAL_RESET_COORDINATE_OFFSET; break; - default: FAIL(STATUS_UNSUPPORTED_STATEMENT); + case 17: gc_block.modal.plane_select = PLANE_SELECT_XY; break; + case 18: gc_block.modal.plane_select = PLANE_SELECT_ZX; break; + case 19: gc_block.modal.plane_select = PLANE_SELECT_YZ; break; } break; - case 93: gc.inverse_feed_rate_mode = true; break; - case 94: gc.inverse_feed_rate_mode = false; break; - default: FAIL(STATUS_UNSUPPORTED_STATEMENT); - } - break; + case 90: case 91: + word_bit = MODAL_GROUP_G3; + if (int_value == 90) { gc_block.modal.distance = DISTANCE_MODE_ABSOLUTE; } // G90 + else { gc_block.modal.distance = DISTANCE_MODE_INCREMENTAL; } // G91 + break; + case 93: case 94: + word_bit = MODAL_GROUP_G5; + if (int_value == 93) { gc_block.modal.feed_rate = FEED_RATE_MODE_INVERSE_TIME; } // G93 + else { gc_block.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; } // G94 + break; + case 20: case 21: + word_bit = MODAL_GROUP_G6; + if (int_value == 20) { gc_block.modal.units = UNITS_MODE_INCHES; } // G20 + else { gc_block.modal.units = UNITS_MODE_MM; } // G21 + break; + case 54: case 55: case 56: case 57: case 58: case 59: + // NOTE: G59.x are not supported. (But their int_values would be 60, 61, and 62.) + word_bit = MODAL_GROUP_G12; + gc_block.modal.coord_select = int_value-54; // Shift to array indexing. + break; + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] + } + if (mantissa > 0) { FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); } // [Unsupported or invalid Gxx.x command] + // Check for more than one command per modal group violations in the current block + // NOTE: Variable 'word_bit' is always assigned, if the command is valid. + if ( bit_istrue(command_words,bit(word_bit)) ) { FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); } + bit_true(command_words,bit(word_bit)); + break; + case 'M': - // Set modal group values + + // Determine 'M' command and its modal group + if (mantissa > 0) { FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); } // [No Mxx.x commands] switch(int_value) { - case 0: case 1: case 2: case 30: group_number = MODAL_GROUP_4; break; - case 3: case 4: case 5: group_number = MODAL_GROUP_7; break; - case 7: case 8: case 9: group_number = MODAL_GROUP_8; break; - } - // Set 'M' commands - switch(int_value) { - case 0: gc.program_flow = PROGRAM_FLOW_PAUSED; break; // Program pause - case 1: break; // Optional stop not supported. Ignore. - case 2: case 30: gc.program_flow = PROGRAM_FLOW_COMPLETED; break; // Program end and reset - case 3: gc.spindle_direction = SPINDLE_ENABLE_CW; break; - case 4: gc.spindle_direction = SPINDLE_ENABLE_CCW; break; - case 5: gc.spindle_direction = SPINDLE_DISABLE; break; - #ifdef ENABLE_M7 - case 7: gc.coolant_mode = COOLANT_MIST_ENABLE; break; - #endif - case 8: gc.coolant_mode = COOLANT_FLOOD_ENABLE; break; - case 9: gc.coolant_mode = COOLANT_DISABLE; break; - default: FAIL(STATUS_UNSUPPORTED_STATEMENT); + case 0: case 1: case 2: case 30: + word_bit = MODAL_GROUP_M4; + switch(int_value) { + case 0: gc_block.modal.program_flow = PROGRAM_FLOW_PAUSED; break; // Program pause + case 1: break; // Optional stop not supported. Ignore. + case 2: case 30: gc_block.modal.program_flow = PROGRAM_FLOW_COMPLETED; break; // Program end and reset + } + break; + case 3: case 4: case 5: + word_bit = MODAL_GROUP_M7; + switch(int_value) { + case 3: gc_block.modal.spindle = SPINDLE_ENABLE_CW; break; + case 4: gc_block.modal.spindle = SPINDLE_ENABLE_CCW; break; + case 5: gc_block.modal.spindle = SPINDLE_DISABLE; break; + } + break; + #ifdef ENABLE_M7 + case 7: + #endif + case 8: case 9: + word_bit = MODAL_GROUP_M8; + switch(int_value) { + #ifdef ENABLE_M7 + case 7: gc_block.modal.coolant = COOLANT_MIST_ENABLE; break; + #endif + case 8: gc_block.modal.coolant = COOLANT_FLOOD_ENABLE; break; + case 9: gc_block.modal.coolant = COOLANT_DISABLE; break; + } + break; + default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command] } + + // Check for more than one command per modal group violations in the current block + // NOTE: Variable 'word_bit' is always assigned, if the command is valid. + if ( bit_istrue(command_words,bit(word_bit)) ) { FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); } + bit_true(command_words,bit(word_bit)); break; - } - // Check for modal group multiple command violations in the current block - if (group_number) { - if ( bit_istrue(modal_group_words,bit(group_number)) ) { - FAIL(STATUS_MODAL_GROUP_VIOLATION); - } else { - bit_true(modal_group_words,bit(group_number)); - } - group_number = MODAL_GROUP_NONE; // Reset for next command. - } - } - - // If there were any errors parsing this line, we will return right away with the bad news - if (gc.status_code) { return(gc.status_code); } + + // NOTE: All remaining letters assign values. + default: - /* Pass 2: Parameters. All units converted according to current block commands. Position - parameters are converted and flagged to indicate a change. These can have multiple connotations - for different commands. Each will be converted to their proper value upon execution. - NOTE: Grbl unconventionally pre-converts these parameter values based on the block G and M - commands. This is set out of the order of execution defined by NIST only for code efficiency/size - purposes, but should not affect proper g-code execution. */ - float p = 0; - uint8_t l = 0; - char_counter = 0; - while(next_statement(&letter, &value, line, &char_counter)) { - switch(letter) { - case 'G': case 'M': break; // Ignore command statements and line numbers - case 'F': - if (value <= 0) { FAIL(STATUS_INVALID_STATEMENT); } // Must be greater than zero - if (gc.inverse_feed_rate_mode) { - inverse_feed_rate = to_millimeters(value); // seconds per motion for this motion only - } else { - gc.feed_rate = to_millimeters(value); // millimeters per minute + /* Non-Command Words: This initial parsing phase only checks for repeats of the remaining + legal g-code words and stores their value. Error-checking is performed later since some + words (I,J,K,L,P,R) have multiple connotations and/or depend on the issued commands. */ + switch(letter){ + // case 'A': // Not supported + // case 'B': // Not supported + // case 'C': // Not supported + // case 'D': // Not supported + case 'F': word_bit = WORD_F; gc_block.values.f = value; break; + // case 'H': // Not supported + case 'I': word_bit = WORD_I; gc_block.values.ijk[X_AXIS] = value; ijk_words |= (1< MAX_LINE_NUMBER) { FAIL(STATUS_GCODE_INVALID_LINE_NUMBER); } // [Exceeds max line number] } + // bit_false(value_words,bit(WORD_N)); // NOTE: Single-meaning value word. Set at end of error-checking. - // [G4,G10,G28,G30,G92,G92.1]: Perform dwell, set coordinate system data, homing, or set axis offsets. - // NOTE: These commands are in the same modal group, hence are mutually exclusive. G53 is in this - // modal group and do not effect these actions. - switch (non_modal_action) { - case NON_MODAL_DWELL: - if (p < 0) { // Time cannot be negative. - FAIL(STATUS_INVALID_STATEMENT); + // Track for unused words at the end of error-checking. + // NOTE: Single-meaning value words are removed all at once at the end of error-checking, because + // they are always used when present. This was done to save a few bytes of flash. For clarity, the + // single-meaning value words may be removed as they are used. Also, axis words are treated in the + // same way. If there is an axis explicit/implicit command, XYZ words are always used and are + // are removed at the end of error-checking. + + // [1. Comments ]: MSG's NOT SUPPORTED. Comment handling performed by protocol. + + // [2. Set feed rate mode ]: G93 F word missing with G1,G2/3 active, implicitly or explicitly. Feed rate + // is not defined after switching to G94 from G93. + if (gc_block.modal.feed_rate == FEED_RATE_MODE_INVERSE_TIME) { // = G93 + // NOTE: G38 can also operate in inverse time, but is undefined as an error. Missing F word check added here. + if (axis_explicit == AXIS_COMMAND_MOTION_MODE) { + if ((gc_block.modal.motion != MOTION_MODE_NONE) || (gc_block.modal.motion != MOTION_MODE_SEEK)) { + if (bit_isfalse(value_words,bit(WORD_F))) { FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); } // [F word missing] + } + } + // NOTE: It seems redundant to check for an F word to be passed after switching from G94 to G93. We would + // accomplish the exact same thing if the feed rate value is always reset to zero and undefined after each + // inverse time block, since the commands that use this value already perform undefined checks. This would + // also allow other commands, following this switch, to execute and not error out needlessly. This code is + // combined with the above feed rate mode and the below set feed rate error-checking. + + // [3. Set feed rate ]: F is negative (done.) + // - In inverse time mode: Always implicitly zero the feed rate value before and after block completion. + // NOTE: If in G93 mode or switched into it from G94, just keep F value as initialized zero or passed F word + // value in the block. If no F word is passed with a motion command that requires a feed rate, this will error + // out in the motion modes error-checking. However, if no F word is passed with NO motion command that requires + // a feed rate, we simply move on and the state feed rate value gets updated to zero and remains undefined. + } else { // = G94 + // - In units per mm mode: If F word passed, ensure value is in mm/min, otherwise push last state value. + if (gc_state.modal.feed_rate == FEED_RATE_MODE_UNITS_PER_MIN) { // Last state is also G94 + if (bit_istrue(value_words,bit(WORD_F))) { + if (gc_block.modal.units == UNITS_MODE_INCHES) { gc_block.values.f *= MM_PER_INCH; } } else { - // Ignore dwell in check gcode modes - if (sys.state != STATE_CHECK_MODE) { mc_dwell(p); } + gc_block.values.f = gc_state.feed_rate; // Push last state feed rate + } + } // Else, switching to G94 from G93, so don't push last state feed rate. Its undefined or the passed F word value. + } + // bit_false(value_words,bit(WORD_F)); // NOTE: Single-meaning value word. Set at end of error-checking. + + // [4. Set spindle speed ]: S is negative (done.) + if (bit_isfalse(value_words,bit(WORD_S))) { gc_block.values.s = gc_state.spindle_speed; } + // bit_false(value_words,bit(WORD_S)); // NOTE: Single-meaning value word. Set at end of error-checking. + + // [5. Select tool ]: NOT SUPPORTED. T is negative (done.) Not an integer. Greater than max tool value. + // bit_false(value_words,bit(WORD_T)); // NOTE: Single-meaning value word. Set at end of error-checking. + + // [6. Change tool ]: N/A + // [7. Spindle control ]: N/A + // [8. Coolant control ]: N/A + // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. + + // [10. Dwell ]: P value missing. P is negative (done.) NOTE: See below. + if (gc_block.non_modal_command == NON_MODAL_DWELL) { + if (bit_isfalse(value_words,bit(WORD_P))) { FAIL(STATUS_GCODE_VALUE_WORD_MISSING); } // [P word missing] + bit_false(value_words,bit(WORD_P)); + } + + // [11. Set active plane ]: N/A + switch (gc_block.modal.plane_select) { + case PLANE_SELECT_XY: + axis_0 = X_AXIS; + axis_1 = Y_AXIS; + axis_linear = Z_AXIS; + break; + case PLANE_SELECT_ZX: + axis_0 = Z_AXIS; + axis_1 = X_AXIS; + axis_linear = Y_AXIS; + break; + default: // case PLANE_SELECT_YZ: + axis_0 = Y_AXIS; + axis_1 = Z_AXIS; + axis_linear = X_AXIS; + } + + // [12. Set length units ]: N/A + // Pre-convert XYZ coordinate values to millimeters, if applicable. + uint8_t idx; + if (gc_block.modal.units == UNITS_MODE_INCHES) { + for (idx=0; idx N_COORDINATE_SYSTEM) { FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); } // [Greater than N sys] + if (gc_state.modal.coord_select != gc_block.modal.coord_select) { + if (!(settings_read_coord_data(gc_block.modal.coord_select,coordinate_data))) { FAIL(STATUS_SETTING_READ_FAIL); } + } + } + + // [16. Set path control mode ]: NOT SUPPORTED. + // [17. Set distance mode ]: N/A. G90.1 and G91.1 NOT SUPPORTED. + // [18. Set retract mode ]: NOT SUPPORTED. + + // [19. Remaining non-modal actions ]: Check go to predefined position, set G10, or set axis offsets. + // NOTE: We need to separate the non-modal commands that are axis-explicit (G10/G28/G30/G92), as these + // commands all treat axis words differently. G10 as absolute offsets or computes current position as + // the axis value, G92 similarly to G10 L20, and G28/30 as an intermediate target position that observes + // all the current coordinate system and G92 offsets. + switch (gc_block.non_modal_command) { + case NON_MODAL_SET_COORDINATE_DATA: + // [G10 Errors]: L missing and is not 2 or 20. P word missing. (Negative P value done.) + // [G10 L2 Errors]: R word NOT SUPPORTED. P value not 0 to nCoordSys(max 9). Axis words missing (done.) + // [G10 L20 Errors]: P must be 0 to nCoordSys(max 9). Axis words missing (done.) + if (bit_isfalse(value_words,((1< N_COORDINATE_SYSTEM) { FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); } // [Greater than N sys] + if (gc_block.values.l != 20) { + if (gc_block.values.l == 2) { + if (bit_istrue(value_words,bit(WORD_R))) { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [G10 L2 R not supported] + } else { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [Unsupported L] + } + bit_false(value_words,(bit(WORD_L)|bit(WORD_P))); + + // Load EEPROM coordinate data and pre-calculate the new coordinate data. + if (int_value > 0) { int_value--; } // Adjust P1-P6 index to EEPROM coordinate data indexing. + else { int_value = gc_state.modal.coord_select; } // Index P0 as the active coordinate system + if (!settings_read_coord_data(int_value,parameter_data)) { FAIL(STATUS_SETTING_READ_FAIL); } // [EEPROM read fail] + for (idx=0; idx N_COORDINATE_SYSTEM)) { // L2 and L20. P1=G54, P2=G55, ... - FAIL(STATUS_UNSUPPORTED_STATEMENT); - } else if (!axis_words && l==2) { // No axis words. - FAIL(STATUS_INVALID_STATEMENT); - } else { - if (int_value > 0) { int_value--; } // Adjust P1-P6 index to EEPROM coordinate data indexing. - else { int_value = gc.coord_select; } // Index P0 as the active coordinate system - float coord_data[N_AXIS]; - if (!settings_read_coord_data(int_value,coord_data)) { return(STATUS_SETTING_READ_FAIL); } - // Update axes defined only in block. Always in machine coordinates. Can change non-active system. - for (idx=0; idx C -----------------+--------------- T <- [x,y] + | <------ d/2 ---->| + + C - Current position + T - Target position + O - center of circle that pass through both C and T + d - distance from C to T + r - designated radius + h - distance from center of CT to O + + Expanding the equations: + + d -> sqrt(x^2 + y^2) + h -> sqrt(4 * r^2 - x^2 - y^2)/2 + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + + Which can be written: + + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 + + Which we for size and speed reasons optimize to: + + h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2) + i = (x - (y * h_x2_div_d))/2 + j = (y + (x * h_x2_div_d))/2 + */ + + // First, use h_x2_div_d to compute 4*h^2 to check if it is negative or r is smaller + // than d. If so, the sqrt of a negative number is complex and error out. + float h_x2_div_d = 4.0 * gc_block.values.r*gc_block.values.r - x*x - y*y; + + if (h_x2_div_d < 0) { FAIL(STATUS_GCODE_ARC_RADIUS_ERROR); } // [Arc radius error] + + // Finish computing h_x2_div_d. + h_x2_div_d = -sqrt(h_x2_div_d)/hypot_f(x,y); // == -(h * 2 / d) + // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) + if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) { h_x2_div_d = -h_x2_div_d; } + + /* The counter clockwise circle lies to the left of the target direction. When offset is positive, + the left hand circle will be generated - when it is negative the right hand circle is generated. + + T <-- Target position + + ^ + Clockwise circles with this center | Clockwise circles with this center will have + will have > 180 deg of angular travel | < 180 deg of angular travel, which is a good thing! + \ | / + center of arc when h_x2_div_d is positive -> x <----- | -----> x <- center of arc when h_x2_div_d is negative + | + | + + C <-- Current position + */ + // Negative R is g-code-alese for "I want a circle with more than 180 degrees of travel" (go figure!), + // even though it is advised against ever generating such circles in a single line of g-code. By + // inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of + // travel and thus we get the unadvisably long arcs as prescribed. + if (gc_block.values.r < 0) { + h_x2_div_d = -h_x2_div_d; + gc_block.values.r = -gc_block.values.r; // Finished with r. Set to positive for mc_arc + } + // Complete the operation by calculating the actual center of the arc + gc_block.values.ijk[axis_0] = 0.5*(x-(y*h_x2_div_d)); + gc_block.values.ijk[axis_1] = 0.5*(y+(x*h_x2_div_d)); + + } else { // Arc Center Format Offset Mode + if (!(ijk_words & (bit(axis_0)|bit(axis_1)))) { FAIL(STATUS_GCODE_NO_OFFSETS_IN_PLANE); } // [No offsets in plane] + bit_false(value_words,(bit(WORD_I)|bit(WORD_J)|bit(WORD_K))); + + // Convert IJK values to proper units. + if (gc_block.modal.units == UNITS_MODE_INCHES) { + for (idx=0; idx 0.002) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Arc definition error] + } + break; + case MOTION_MODE_PROBE: + // [G38 Errors]: Target is same current. No axis words. Cutter compensation is enabled. Feed rate + // is undefined. Probe is triggered. + if (gc_check_same_position(gc_state.position, gc_block.values.xyz)) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Invalid target] + if (probe_get_state()) { FAIL(STATUS_GCODE_PROBE_TRIGGERED); } // [Probe triggered] + break; + } + } + } + + // [21. Program flow ]: No error check required. + + // [0. Non-specific error-checks]: Complete unused value words check, i.e. IJK used when in arc + // radius mode, or axis words that aren't used in the block. + bit_false(value_words,(bit(WORD_N)|bit(WORD_F)|bit(WORD_S)|bit(WORD_T))); // Remove single-meaning value words. + if (axis_explicit) { bit_false(value_words,(bit(WORD_X)|bit(WORD_Y)|bit(WORD_Z))); } // Remove axis words. + if (value_words) { FAIL(STATUS_GCODE_UNUSED_WORDS); } // [Unused words] + + + /* ------------------------------------------------------------------------------------- + STEP 4: EXECUTE!! + Assumes that all error-checking has been completed and no failure modes exist. We just + need to update the state and execute the block according to the order-of-execution. + */ + + // [1. Comments feedback ]: NOT SUPPORTED + + // [2. Set feed rate mode ]: + gc_state.modal.feed_rate = gc_block.modal.feed_rate; + + // [3. Set feed rate ]: + gc_state.feed_rate = gc_block.values.f; // Always copy this value. See feed rate error-checking. + + // [4. Set spindle speed ]: + if (gc_state.spindle_speed != gc_block.values.s) { + gc_state.spindle_speed = gc_block.values.s; + + // Update running spindle only if not in check mode and not already enabled. + if (gc_state.modal.spindle != SPINDLE_DISABLE) { spindle_run(gc_state.modal.spindle, gc_state.spindle_speed); } + } + + // [5. Select tool ]: NOT SUPPORTED + + // [6. Change tool ]: NOT SUPPORTED + + // [7. Spindle control ]: + if (gc_state.modal.spindle != gc_block.modal.spindle) { + gc_state.modal.spindle = gc_block.modal.spindle; + // Update spindle control and apply spindle speed when enabling it in this block. + spindle_run(gc_state.modal.spindle, gc_state.spindle_speed); + } + + // [8. Coolant control ]: + if (gc_state.modal.coolant != gc_block.modal.coolant) { + gc_state.modal.coolant = gc_block.modal.coolant; + coolant_run(gc_state.modal.coolant); + } + + // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED + + // [10. Dwell ]: + if (gc_block.non_modal_command == NON_MODAL_DWELL) { mc_dwell(gc_block.values.p); } + + // [11. Set active plane ]: + gc_state.modal.plane_select = gc_block.modal.plane_select; + + // [12. Set length units ]: + gc_state.modal.units = gc_block.modal.units; + + // [13. Cutter radius compensation ]: NOT SUPPORTED + + // [14. Cutter length compensation ]: NOT SUPPORTED + + // [15. Coordinate system selection ]: + if (gc_state.modal.coord_select != gc_block.modal.coord_select) { + gc_state.modal.coord_select = gc_block.modal.coord_select; + memcpy(gc_state.coord_system,coordinate_data,sizeof(coordinate_data)); + } + + // [16. Set path control mode ]: NOT SUPPORTED + + // [17. Set distance mode ]: + gc_state.modal.distance = gc_block.modal.distance; + + // [18. Set retract mode ]: NOT SUPPORTED + + // [19. Go to predefined position, Set G10, or Set axis offsets ]: + switch(gc_block.non_modal_command) { + case NON_MODAL_SET_COORDINATE_DATA: + +// TODO: See if I can clean up this int_value. + int_value = trunc(gc_block.values.p); // Convert p value to int. + if (int_value > 0) { int_value--; } // Adjust P1-P6 index to EEPROM coordinate data indexing. + else { int_value = gc_state.modal.coord_select; } // Index P0 as the active coordinate system + + settings_write_coord_data(int_value,parameter_data); + // Update system coordinate system if currently active. + if (gc_state.modal.coord_select == int_value) { memcpy(gc_state.coord_system,parameter_data,sizeof(parameter_data)); } break; case NON_MODAL_GO_HOME_0: case NON_MODAL_GO_HOME_1: // Move to intermediate position before going home. Obeys current coordinate system and offsets // and absolute and incremental modes. - if (axis_words) { - // Apply absolute mode coordinate offsets or incremental mode offsets. - for (idx=0; idx 'Z')) { - FAIL(STATUS_EXPECTED_COMMAND_LETTER); - return(0); - } - (*char_counter)++; - if (!read_float(line, char_counter, float_ptr)) { - FAIL(STATUS_BAD_NUMBER_FORMAT); - return(0); - }; - return(1); -} - - -static void gc_convert_arc_radius_mode(float *target) -{ -/* We need to calculate the center of the circle that has the designated radius and passes - through both the current position and the target position. This method calculates the following - set of equations where [x,y] is the vector from current to target position, d == magnitude of - that vector, h == hypotenuse of the triangle formed by the radius of the circle, the distance to - the center of the travel vector. A vector perpendicular to the travel vector [-y,x] is scaled to the - length of h [-y/d*h, x/d*h] and added to the center of the travel vector [x/2,y/2] to form the new point - [i,j] at [x/2-y/d*h, y/2+x/d*h] which will be the center of our arc. - - d^2 == x^2 + y^2 - h^2 == r^2 - (d/2)^2 - i == x/2 - y/d*h - j == y/2 + x/d*h - - O <- [i,j] - - | - r - | - - | - - | h - - | - [0,0] -> C -----------------+--------------- T <- [x,y] - | <------ d/2 ---->| - - C - Current position - T - Target position - O - center of circle that pass through both C and T - d - distance from C to T - r - designated radius - h - distance from center of CT to O - - Expanding the equations: - - d -> sqrt(x^2 + y^2) - h -> sqrt(4 * r^2 - x^2 - y^2)/2 - i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 - j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 - - Which can be written: - - i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 - j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 - - Which we for size and speed reasons optimize to: - - h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2) - i = (x - (y * h_x2_div_d))/2 - j = (y + (x * h_x2_div_d))/2 */ - - // Calculate the change in position along each selected axis - float x = target[gc.plane_axis_0]-gc.position[gc.plane_axis_0]; - float y = target[gc.plane_axis_1]-gc.position[gc.plane_axis_1]; - - clear_vector(gc.arc_offset); - // First, use h_x2_div_d to compute 4*h^2 to check if it is negative or r is smaller - // than d. If so, the sqrt of a negative number is complex and error out. - float h_x2_div_d = 4 * gc.arc_radius*gc.arc_radius - x*x - y*y; - if (h_x2_div_d < 0) { FAIL(STATUS_ARC_RADIUS_ERROR); return; } - // Finish computing h_x2_div_d. - h_x2_div_d = -sqrt(h_x2_div_d)/hypot(x,y); // == -(h * 2 / d) - // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) - if (gc.motion_mode == MOTION_MODE_CCW_ARC) { h_x2_div_d = -h_x2_div_d; } - - /* The counter clockwise circle lies to the left of the target direction. When offset is positive, - the left hand circle will be generated - when it is negative the right hand circle is generated. - - - T <-- Target position - - ^ - Clockwise circles with this center | Clockwise circles with this center will have - will have > 180 deg of angular travel | < 180 deg of angular travel, which is a good thing! - \ | / - center of arc when h_x2_div_d is positive -> x <----- | -----> x <- center of arc when h_x2_div_d is negative - | - | - - C <-- Current position */ - - - // Negative R is g-code-alese for "I want a circle with more than 180 degrees of travel" (go figure!), - // even though it is advised against ever generating such circles in a single line of g-code. By - // inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of - // travel and thus we get the unadvisably long arcs as prescribed. - if (gc.arc_radius < 0) { - h_x2_div_d = -h_x2_div_d; - gc.arc_radius = -gc.arc_radius; // Finished with r. Set to positive for mc_arc - } - // Complete the operation by calculating the actual center of the arc - gc.arc_offset[gc.plane_axis_0] = 0.5*(x-(y*h_x2_div_d)); - gc.arc_offset[gc.plane_axis_1] = 0.5*(y+(x*h_x2_div_d)); -} + /* Not supported: @@ -648,14 +944,13 @@ static void gc_convert_arc_radius_mode(float *target) - A,B,C-axes - Evaluation of expressions - Variables - - Probing - Override control (TBD) - Tool changes - Switches (*) Indicates optional parameter, enabled through config.h and re-compile group 0 = {G92.2, G92.3} (Non modal: Cancel and re-enable G92 offsets) - group 1 = {G38.2, G81 - G89} (Motion modes: straight probe, canned cycles) + group 1 = {G81 - G89} (Motion modes: Canned cycles) group 4 = {M1} (Optional stop, ignored) group 6 = {M6} (Tool change) group 8 = {*M7} enable mist coolant diff --git a/gcode.h b/gcode.h index acd7046..3acba8f 100644 --- a/gcode.h +++ b/gcode.h @@ -29,68 +29,151 @@ // mutually exclusive, or cannot exist on the same line, because they each toggle a state or execute // a unique motion. These are defined in the NIST RS274-NGC v3 g-code standard, available online, // and are similar/identical to other g-code interpreters by manufacturers (Haas,Fanuc,Mazak,etc). -#define MODAL_GROUP_NONE 0 -#define MODAL_GROUP_0 1 // [G4,G10,G28,G30,G53,G92,G92.1] Non-modal -#define MODAL_GROUP_1 2 // [G0,G1,G2,G3,G38.2,G80] Motion -#define MODAL_GROUP_2 3 // [G17,G18,G19] Plane selection -#define MODAL_GROUP_3 4 // [G90,G91] Distance mode -#define MODAL_GROUP_4 5 // [M0,M1,M2,M30] Stopping -#define MODAL_GROUP_5 6 // [G93,G94] Feed rate mode -#define MODAL_GROUP_6 7 // [G20,G21] Units -#define MODAL_GROUP_7 8 // [M3,M4,M5] Spindle turning -#define MODAL_GROUP_8 9 // [M7,M8,M9] Coolant control -#define MODAL_GROUP_12 10 // [G54,G55,G56,G57,G58,G59] Coordinate system selection +// NOTE: Modal group define values must be sequential and starting from zero. +#define MODAL_GROUP_G0 0 // [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1] Non-modal +#define MODAL_GROUP_G1 1 // [G0,G1,G2,G3,G38.2,G80] Motion +#define MODAL_GROUP_G2 2 // [G17,G18,G19] Plane selection +#define MODAL_GROUP_G3 3 // [G90,G91] Distance mode +#define MODAL_GROUP_G5 4 // [G93,G94] Feed rate mode +#define MODAL_GROUP_G6 5 // [G20,G21] Units +#define MODAL_GROUP_G12 6 // [G54,G55,G56,G57,G58,G59] Coordinate system selection + +#define MODAL_GROUP_M4 7 // [M0,M1,M2,M30] Stopping +#define MODAL_GROUP_M7 8 // [M3,M4,M5] Spindle turning +#define MODAL_GROUP_M8 9 // [M7,M8,M9] Coolant control + +#define OTHER_INPUT_F 10 +#define OTHER_INPUT_S 11 +#define OTHER_INPUT_T 12 // Define command actions for within execution-type modal groups (motion, stopping, non-modal). Used // internally by the parser to know which command to execute. -#define MOTION_MODE_SEEK 0 // G0 -#define MOTION_MODE_LINEAR 1 // G1 -#define MOTION_MODE_CW_ARC 2 // G2 -#define MOTION_MODE_CCW_ARC 3 // G3 -#define MOTION_MODE_PROBE 4 // G38.x -#define MOTION_MODE_CANCEL 5 // G80 -#define PROGRAM_FLOW_RUNNING 0 -#define PROGRAM_FLOW_PAUSED 1 // M0, M1 -#define PROGRAM_FLOW_COMPLETED 2 // M2, M30 - -#define NON_MODAL_NONE 0 +// Modal Group 0: Non-modal actions +#define NON_MODAL_NO_ACTION 0 // (Default: Must be zero) #define NON_MODAL_DWELL 1 // G4 #define NON_MODAL_SET_COORDINATE_DATA 2 // G10 #define NON_MODAL_GO_HOME_0 3 // G28 #define NON_MODAL_SET_HOME_0 4 // G28.1 #define NON_MODAL_GO_HOME_1 5 // G30 #define NON_MODAL_SET_HOME_1 6 // G30.1 -#define NON_MODAL_SET_COORDINATE_OFFSET 7 // G92 -#define NON_MODAL_RESET_COORDINATE_OFFSET 8 //G92.1 +#define NON_MODAL_ABSOLUTE_OVERRIDE 7 // G53 +#define NON_MODAL_SET_COORDINATE_OFFSET 8 // G92 +#define NON_MODAL_RESET_COORDINATE_OFFSET 9 //G92.1 + +// Modal Group 1: Motion modes +#define MOTION_MODE_SEEK 0 // G0 (Default: Must be zero) +#define MOTION_MODE_LINEAR 1 // G1 +#define MOTION_MODE_CW_ARC 2 // G2 +#define MOTION_MODE_CCW_ARC 3 // G3 +#define MOTION_MODE_PROBE 4 // G38.2 +#define MOTION_MODE_NONE 5 // G80 + +// Modal Group 2: Plane select +#define PLANE_SELECT_XY 0 // G17 (Default: Must be zero) +#define PLANE_SELECT_ZX 1 // G18 +#define PLANE_SELECT_YZ 2 // G19 + +// Modal Group 3: Distance mode +#define DISTANCE_MODE_ABSOLUTE 0 // G90 (Default: Must be zero) +#define DISTANCE_MODE_INCREMENTAL 1 // G91 + +// Modal Group 4: Program flow +#define PROGRAM_FLOW_RUNNING 0 // (Default: Must be zero) +#define PROGRAM_FLOW_PAUSED 1 // M0, M1 +#define PROGRAM_FLOW_COMPLETED 2 // M2, M30 + +// Modal Group 5: Feed rate mode +#define FEED_RATE_MODE_UNITS_PER_MIN 0 // G94 (Default: Must be zero) +#define FEED_RATE_MODE_INVERSE_TIME 1 // G93 + +// Modal Group 6: Units mode +#define UNITS_MODE_MM 0 // G21 (Default: Must be zero) +#define UNITS_MODE_INCHES 1 // G20 + +// Modal Group 7: Spindle control +#define SPINDLE_DISABLE 0 // M5 (Default: Must be zero) +#define SPINDLE_ENABLE_CW 1 // M3 +#define SPINDLE_ENABLE_CCW 2 // M4 + +// Modal Group 8: Coolant control +#define COOLANT_DISABLE 0 // M9 (Default: Must be zero) +#define COOLANT_MIST_ENABLE 1 // M7 +#define COOLANT_FLOOD_ENABLE 2 // M8 + +// Modal Group 12: Active work coordinate system +// N/A: Stores coordinate system value (54-59) to change to. + +#define WORD_F 0 +#define WORD_I 1 +#define WORD_J 2 +#define WORD_K 3 +#define WORD_L 4 +#define WORD_N 5 +#define WORD_P 6 +#define WORD_R 7 +#define WORD_S 8 +#define WORD_T 9 +#define WORD_X 10 +#define WORD_Y 11 +#define WORD_Z 12 + + + + +// NOTE: When this struct is zeroed, the above defines set the defaults for the system. +typedef struct { + uint8_t motion; // {G0,G1,G2,G3,G80} + uint8_t feed_rate; // {G93,G94} + uint8_t units; // {G20,G21} + uint8_t distance; // {G90,G91} + uint8_t plane_select; // {G17,G18,G19} + uint8_t coord_select; // {G54,G55,G56,G57,G58,G59} + uint8_t program_flow; // {M0,M1,M2,M30} + uint8_t coolant; // {M7,M8,M9} + uint8_t spindle; // {M3,M4,M5} +} gc_modal_t; typedef struct { - uint8_t status_code; // Parser status for current block - uint8_t motion_mode; // {G0, G1, G2, G3, G80} - uint8_t inverse_feed_rate_mode; // {G93, G94} - uint8_t inches_mode; // 0 = millimeter mode, 1 = inches mode {G20, G21} - uint8_t absolute_mode; // 0 = relative motion, 1 = absolute motion {G90, G91} - uint8_t program_flow; // {M0, M1, M2, M30} - uint8_t coolant_mode; // 0 = Disable, 1 = Flood Enable, 2 = Mist Enable {M8, M9} - uint8_t spindle_direction; // 1 = CW, 2 = CCW, 0 = Stop {M3, M4, M5} + float f; // Feed + float ijk[3]; // I,J,K Axis arc offsets + uint8_t l; // G10 or canned cycles parameters + int32_t n; // Line number + float p; // G10 or dwell parameters + // float q; // G82 peck drilling + float r; // Arc radius + float s; // Spindle speed + // uint8_t t; // Tool selection + float xyz[3]; // X,Y,Z Translational axes +} gc_values_t; + + +typedef struct { + gc_modal_t modal; + float spindle_speed; // RPM float feed_rate; // Millimeters/min - float position[N_AXIS]; // Where the interpreter considers the tool to be at this point in the code uint8_t tool; - uint8_t plane_axis_0, - plane_axis_1, - plane_axis_2; // The axes of the selected plane - uint8_t coord_select; // Active work coordinate system number. Default: 0=G54. + + float position[N_AXIS]; // Where the interpreter considers the tool to be at this point in the code + float coord_system[N_AXIS]; // Current work coordinate system (G54+). Stores offset from absolute machine - // position in mm. Loaded from EEPROM when called. + // position in mm. Loaded from EEPROM when called. float coord_offset[N_AXIS]; // Retains the G92 coordinate offset (work coordinates) relative to // machine zero in mm. Non-persistent. Cleared upon reset and boot. - - float arc_radius; - float arc_offset[N_AXIS]; - } parser_state_t; -extern parser_state_t gc; +extern parser_state_t gc_state; + +typedef struct { +// uint16_t command_words; // NOTE: If this bitflag variable fills, G and M words can be separated. +// uint16_t value_words; + + uint8_t non_modal_command; + gc_modal_t modal; + gc_values_t values; + +} parser_block_t; +extern parser_block_t gc_block; // Initialize the parser void gc_init(); diff --git a/motion_control.c b/motion_control.c index 6fda0c2..321dfe9 100644 --- a/motion_control.c +++ b/motion_control.c @@ -97,16 +97,15 @@ void mc_line(float *target, float feed_rate, uint8_t invert_feed_rate) // of each segment is configured in settings.arc_tolerance, which is defined to be the maximum normal // distance from segment to the circle when the end points both lie on the circle. #ifdef USE_LINE_NUMBERS -void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8_t axis_1, - uint8_t axis_linear, float feed_rate, uint8_t invert_feed_rate, float radius, uint8_t isclockwise, int32_t line_number) +void mc_arc(float *position, float *target, float *offset, float radius, float feed_rate, + uint8_t invert_feed_rate, uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, int32_t line_number) #else -void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8_t axis_1, - uint8_t axis_linear, float feed_rate, uint8_t invert_feed_rate, float radius, uint8_t isclockwise) +void mc_arc(float *position, float *target, float *offset, float radius, float feed_rate, + uint8_t invert_feed_rate, uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear) #endif -{ +{ float center_axis0 = position[axis_0] + offset[axis_0]; float center_axis1 = position[axis_1] + offset[axis_1]; - float linear_travel = target[axis_linear] - position[axis_linear]; float r_axis0 = -offset[axis_0]; // Radius vector from center to current location float r_axis1 = -offset[axis_1]; float rt_axis0 = target[axis_0] - center_axis0; @@ -114,7 +113,7 @@ void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8 // CCW angle between position and target from circle center. Only one atan2() trig computation required. float angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1); - if (isclockwise) { // Correct atan2 output per direction + if (gc_state.modal.motion == MOTION_MODE_CW_ARC) { // Correct atan2 output per direction if (angular_travel >= 0) { angular_travel -= 2*M_PI; } } else { if (angular_travel <= 0) { angular_travel += 2*M_PI; } @@ -123,10 +122,8 @@ void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8 // NOTE: Segment end points are on the arc, which can lead to the arc diameter being smaller by up to // (2x) settings.arc_tolerance. For 99% of users, this is just fine. If a different arc segment fit // is desired, i.e. least-squares, midpoint on arc, just change the mm_per_arc_segment calculation. - // Computes: mm_per_arc_segment = sqrt(4*arc_tolerance*(2*radius-arc_tolerance)), - // segments = millimeters_of_travel/mm_per_arc_segment - float millimeters_of_travel = hypot(angular_travel*radius, fabs(linear_travel)); - uint16_t segments = floor(0.5*millimeters_of_travel/ + // For the intended uses of Grbl, this value shouldn't exceed 2000 for the strictest of cases. + uint16_t segments = floor(fabs(0.5*angular_travel*radius)/ sqrt(settings.arc_tolerance*(2*radius - settings.arc_tolerance)) ); if (segments) { @@ -136,7 +133,7 @@ void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8 if (invert_feed_rate) { feed_rate *= segments; } float theta_per_segment = angular_travel/segments; - float linear_per_segment = linear_travel/segments; + float linear_per_segment = (target[axis_linear] - position[axis_linear])/segments; /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, and phi is the angle of rotation. Solution approach by Jens Geisler. @@ -166,17 +163,13 @@ void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8 float sin_T = theta_per_segment*0.16666667*(cos_T + 4.0); cos_T *= 0.5; - float arc_target[N_AXIS]; float sin_Ti; float cos_Ti; float r_axisi; uint16_t i; uint8_t count = 0; - // Initialize the linear axis - arc_target[axis_linear] = position[axis_linear]; - - for (i = 1; i (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) @@ -53,7 +53,7 @@ // Read a floating point value from a string. Line points to the input buffer, char_counter // is the indexer pointing to the current character of the line, while float_ptr is // a pointer to the result variable. Returns true when it succeeds -int read_float(char *line, uint8_t *char_counter, float *float_ptr); +uint8_t read_float(char *line, uint8_t *char_counter, float *float_ptr); // Delays variable-defined milliseconds. Compiler compatibility fix for _delay_ms(). void delay_ms(uint16_t ms); @@ -63,4 +63,6 @@ void delay_us(uint32_t us); uint8_t get_direction_mask(uint8_t i); +float hypot_f(float x, float y); + #endif diff --git a/print.c b/print.c index 03e261d..6fe8c2d 100644 --- a/print.c +++ b/print.c @@ -76,32 +76,52 @@ void print_uint8_base2(uint8_t n) serial_write('0' + buf[i - 1]); } -void print_uint32_base10(unsigned long n) +void print_uint8_base10(uint8_t n) { - unsigned char buf[10]; - uint8_t i = 0; - if (n == 0) { serial_write('0'); return; } + + unsigned char buf[3]; + uint8_t i = 0; + + while (n > 0) { + buf[i++] = n % 10 + '0'; + n /= 10; + } + + for (; i > 0; i--) + serial_write(buf[i - 1]); +} + +void print_uint32_base10(unsigned long n) +{ + if (n == 0) { + serial_write('0'); + return; + } + + unsigned char buf[10]; + uint8_t i = 0; while (n > 0) { - buf[i++] = n % 10 + '0'; + buf[i++] = n % 10; n /= 10; } for (; i > 0; i--) - serial_write(buf[i-1]); + serial_write('0' + buf[i-1]); } void printInteger(long n) { if (n < 0) { serial_write('-'); - n = -n; + print_uint32_base10((-n)); + } else { + print_uint32_base10(n); } - print_uint32_base10(n); } // Convert float to string by immediately converting to a long integer, which contains diff --git a/print.h b/print.h index ecd4238..273a554 100644 --- a/print.h +++ b/print.h @@ -35,6 +35,8 @@ void print_uint32_base10(uint32_t n); void print_uint8_base2(uint8_t n); +void print_uint8_base10(uint8_t n); + void printFloat(float n); #endif \ No newline at end of file diff --git a/probe.c b/probe.c index 20b43a8..cad0449 100644 --- a/probe.c +++ b/probe.c @@ -30,13 +30,19 @@ void probe_init() } +// Returns the probe pin state. Triggered = true. Called by gcode parser and probe state monitor. +uint8_t probe_get_state() +{ + return(!(PROBE_PIN & PROBE_MASK)); +} + // Monitors probe pin state and records the system position when detected. Called by the // stepper ISR per ISR tick. // NOTE: This function must be extremely efficient as to not bog down the stepper ISR. void probe_state_monitor() { if (sys.probe_state == PROBE_ACTIVE) { - if (!(PROBE_PIN & PROBE_MASK)) { + if (probe_get_state()) { sys.probe_state = PROBE_OFF; memcpy(sys.probe_position, sys.position, sizeof(float)*N_AXIS); sys.execute |= EXEC_FEED_HOLD; diff --git a/probe.h b/probe.h index e5aef86..294c01b 100644 --- a/probe.h +++ b/probe.h @@ -29,6 +29,9 @@ // Probe pin initialization routine. void probe_init(); +// Returns probe pin state. +uint8_t probe_get_state(); + // Monitors probe pin state and records the system position when detected. Called by the // stepper ISR per ISR tick. void probe_state_monitor(); diff --git a/protocol.c b/protocol.c index 2bf7bcd..c97829b 100644 --- a/protocol.c +++ b/protocol.c @@ -36,37 +36,27 @@ static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. // Directs and executes one line of formatted input from protocol_process. While mostly // incoming streaming g-code blocks, this also directs and executes Grbl internal commands, // such as settings, initiating the homing cycle, and toggling switch states. -// TODO: Eventually re-organize this function to more cleanly organize order of operations, -// which will hopefully reduce some of the current spaghetti logic and dynamic memory usage. static void protocol_execute_line(char *line) { protocol_execute_runtime(); // Runtime command check point. if (sys.abort) { return; } // Bail to calling function upon system abort - uint8_t status; if (line[0] == 0) { // Empty or comment line. Send status message for syncing purposes. - status = STATUS_OK; + report_status_message(STATUS_OK); } else if (line[0] == '$') { // Grbl '$' system command - status = system_execute_line(line); + report_status_message(system_execute_line(line)); + } else if (sys.state == STATE_ALARM) { + // Everything else is gcode. Block if in alarm mode. + report_status_message(STATUS_ALARM_LOCK); + } else { - // Everything else is gcode. Send to g-code parser! Block if in alarm mode. - if (sys.state == STATE_ALARM) { status = STATUS_ALARM_LOCK; } - else { status = gc_execute_line(line); } - - // TODO: Separate the parsing from the g-code execution. Need to re-write the parser - // completely to do this. First parse the line completely, checking for modal group - // errors and storing all of the g-code words. Then, send the stored g-code words to - // a separate g-code executor. This will be more in-line with actual g-code protocol. - - // TODO: Clean up the multi-tasking workflow with the execution of commands. It's a - // bit complicated and patch-worked. Could be made simplier to understand. + // Parse and execute g-code block! + report_status_message(gc_execute_line(line)); } - - report_status_message(status); } @@ -117,12 +107,27 @@ void protocol_main_loop() } } else { if (c <= ' ') { - // Throw away whitepace and control characters + // Throw away whitepace and control characters } else if (c == '/') { - // Block delete not supported. Ignore character. + // Block delete NOT SUPPORTED. Ignore character. + // NOTE: If supported, would simply need to check the system if block delete is enabled. } else if (c == '(') { // Enable comments flag and ignore all characters until ')' or EOL. + // 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. iscomment = true; + // } else if (c == ';') { + // Comment character to EOL NOT SUPPORTED. LinuxCNC definition. Not NIST. + + // TODO: Install '%' feature + // } else if (c == '%') { + // Program start-end percent sign NOT SUPPORTED. + // NOTE: This maybe installed to tell Grbl when a program is running vs manual input, + // where, during a program, the system auto-cycle start will continue to execute + // everything until the next '%' sign. This will help fix resuming issues with certain + // functions that empty the planner buffer to execute its task on-time. + } else if (char_counter >= LINE_BUFFER_SIZE-1) { // Detect line buffer overflow. Report error and reset line buffer. report_status_message(STATUS_OVERFLOW); diff --git a/report.c b/report.c index e766ee5..bed9661 100644 --- a/report.c +++ b/report.c @@ -51,22 +51,16 @@ void report_status_message(uint8_t status_code) } else { printPgmString(PSTR("error: ")); switch(status_code) { - case STATUS_BAD_NUMBER_FORMAT: - printPgmString(PSTR("Bad number format")); break; case STATUS_EXPECTED_COMMAND_LETTER: printPgmString(PSTR("Expected command letter")); break; - case STATUS_UNSUPPORTED_STATEMENT: - printPgmString(PSTR("Unsupported statement")); break; - case STATUS_ARC_RADIUS_ERROR: - printPgmString(PSTR("Invalid radius")); break; - case STATUS_MODAL_GROUP_VIOLATION: - printPgmString(PSTR("Modal group violation")); break; + case STATUS_BAD_NUMBER_FORMAT: + printPgmString(PSTR("Bad number format")); break; case STATUS_INVALID_STATEMENT: printPgmString(PSTR("Invalid statement")); break; + case STATUS_NEGATIVE_VALUE: + printPgmString(PSTR("Value < 0")); break; case STATUS_SETTING_DISABLED: printPgmString(PSTR("Setting disabled")); break; - case STATUS_SETTING_VALUE_NEG: - printPgmString(PSTR("Value < 0.0")); break; case STATUS_SETTING_STEP_PULSE_MIN: printPgmString(PSTR("Value < 3 usec")); break; case STATUS_SETTING_READ_FAIL: @@ -79,6 +73,18 @@ void report_status_message(uint8_t status_code) printPgmString(PSTR("Homing not enabled")); break; case STATUS_OVERFLOW: printPgmString(PSTR("Line overflow")); break; + + // Common g-code parser errors. + case STATUS_GCODE_MODAL_GROUP_VIOLATION: + printPgmString(PSTR("Modal group violation")); break; + case STATUS_GCODE_UNSUPPORTED_COMMAND: + printPgmString(PSTR("Unsupported command")); break; + case STATUS_GCODE_UNDEFINED_FEED_RATE: + printPgmString(PSTR("Undefined feed rate")); break; + default: + // Remaining g-code parser errors with error codes + printPgmString(PSTR("Invalid gcode ID:")); + print_uint8_base10(status_code); // Print error code for user reference } printPgmString(PSTR("\r\n")); } @@ -163,29 +169,28 @@ void report_grbl_settings() { printPgmString(PSTR(" (z accel, mm/sec^2)\r\n$9=")); printFloat(-settings.max_travel[X_AXIS]); // Grbl internally store this as negative. printPgmString(PSTR(" (x max travel, mm)\r\n$10=")); printFloat(-settings.max_travel[Y_AXIS]); // Grbl internally store this as negative. printPgmString(PSTR(" (y max travel, mm)\r\n$11=")); printFloat(-settings.max_travel[Z_AXIS]); // Grbl internally store this as negative. - printPgmString(PSTR(" (z max travel, mm)\r\n$12=")); printInteger(settings.pulse_microseconds); - printPgmString(PSTR(" (step pulse, usec)\r\n$13=")); printFloat(settings.default_feed_rate); - printPgmString(PSTR(" (default feed, mm/min)\r\n$14=")); printInteger(settings.step_invert_mask); + printPgmString(PSTR(" (z max travel, mm)\r\n$12=")); print_uint8_base10(settings.pulse_microseconds); + printPgmString(PSTR(" (step pulse, usec)\r\n$13=")); print_uint8_base10(settings.step_invert_mask); printPgmString(PSTR(" (step port invert mask:")); print_uint8_base2(settings.step_invert_mask); - printPgmString(PSTR(")\r\n$15=")); printInteger(settings.dir_invert_mask); + printPgmString(PSTR(")\r\n$14=")); print_uint8_base10(settings.dir_invert_mask); printPgmString(PSTR(" (dir port invert mask:")); print_uint8_base2(settings.dir_invert_mask); - printPgmString(PSTR(")\r\n$16=")); printInteger(settings.stepper_idle_lock_time); - printPgmString(PSTR(" (step idle delay, msec)\r\n$17=")); printFloat(settings.junction_deviation); - printPgmString(PSTR(" (junction deviation, mm)\r\n$18=")); printFloat(settings.arc_tolerance); - printPgmString(PSTR(" (arc tolerance, mm)\r\n$19=")); printInteger(settings.decimal_places); - printPgmString(PSTR(" (n-decimals, int)\r\n$20=")); printInteger(bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)); - printPgmString(PSTR(" (report inches, bool)\r\n$21=")); printInteger(bit_istrue(settings.flags,BITFLAG_AUTO_START)); - printPgmString(PSTR(" (auto start, bool)\r\n$22=")); printInteger(bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)); - printPgmString(PSTR(" (invert step enable, bool)\r\n$23=")); printInteger(bit_istrue(settings.flags,BITFLAG_INVERT_LIMIT_PINS)); - printPgmString(PSTR(" (invert limit pins, bool)\r\n$24=")); printInteger(bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)); - printPgmString(PSTR(" (soft limits, bool)\r\n$25=")); printInteger(bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE)); - printPgmString(PSTR(" (hard limits, bool)\r\n$26=")); printInteger(bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)); - printPgmString(PSTR(" (homing cycle, bool)\r\n$27=")); printInteger(settings.homing_dir_mask); + printPgmString(PSTR(")\r\n$15=")); print_uint8_base10(settings.stepper_idle_lock_time); + printPgmString(PSTR(" (step idle delay, msec)\r\n$16=")); printFloat(settings.junction_deviation); + printPgmString(PSTR(" (junction deviation, mm)\r\n$17=")); printFloat(settings.arc_tolerance); + printPgmString(PSTR(" (arc tolerance, mm)\r\n$18=")); print_uint8_base10(settings.decimal_places); + printPgmString(PSTR(" (n-decimals, int)\r\n$19=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)); + printPgmString(PSTR(" (report inches, bool)\r\n$20=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_AUTO_START)); + printPgmString(PSTR(" (auto start, bool)\r\n$21=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)); + printPgmString(PSTR(" (invert step enable, bool)\r\n$22=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_LIMIT_PINS)); + printPgmString(PSTR(" (invert limit pins, bool)\r\n$23=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)); + printPgmString(PSTR(" (soft limits, bool)\r\n$24=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE)); + printPgmString(PSTR(" (hard limits, bool)\r\n$25=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)); + printPgmString(PSTR(" (homing cycle, bool)\r\n$26=")); print_uint8_base10(settings.homing_dir_mask); printPgmString(PSTR(" (homing dir invert mask:")); print_uint8_base2(settings.homing_dir_mask); - printPgmString(PSTR(")\r\n$28=")); printFloat(settings.homing_feed_rate); - printPgmString(PSTR(" (homing feed, mm/min)\r\n$29=")); printFloat(settings.homing_seek_rate); - printPgmString(PSTR(" (homing seek, mm/min)\r\n$30=")); printInteger(settings.homing_debounce_delay); - printPgmString(PSTR(" (homing debounce, msec)\r\n$31=")); printFloat(settings.homing_pulloff); + printPgmString(PSTR(")\r\n$27=")); printFloat(settings.homing_feed_rate); + printPgmString(PSTR(" (homing feed, mm/min)\r\n$28=")); printFloat(settings.homing_seek_rate); + printPgmString(PSTR(" (homing seek, mm/min)\r\n$29=")); print_uint8_base10(settings.homing_debounce_delay); + printPgmString(PSTR(" (homing debounce, msec)\r\n$30=")); printFloat(settings.homing_pulloff); printPgmString(PSTR(" (homing pull-off, mm)\r\n")); } @@ -222,16 +227,11 @@ void report_ngc_parameters() } printPgmString(PSTR("[G")); switch (coord_select) { - case 0: printPgmString(PSTR("54:")); break; - case 1: printPgmString(PSTR("55:")); break; - case 2: printPgmString(PSTR("56:")); break; - case 3: printPgmString(PSTR("57:")); break; - case 4: printPgmString(PSTR("58:")); break; - case 5: printPgmString(PSTR("59:")); break; - case 6: printPgmString(PSTR("28:")); break; - case 7: printPgmString(PSTR("30:")); break; - // case 8: printPgmString(PSTR("92:")); break; // G92.2, G92.3 not supported. Hence not stored. - } + case 6: printPgmString(PSTR("28")); break; + case 7: printPgmString(PSTR("30")); break; + default: print_uint8_base10(coord_select+54); break; // G54-G59 + } + printPgmString(PSTR(":")); for (i=0; i