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