diff --git a/changelog.md b/changelog.md index f13185b..07514d6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,21 @@ ## grblHAL changelog +Build 20230429 + +Core: + +* Now allows some $-commands while critical events are active or in sleep mode. E.g. settings commands can be used to configure the controller. + +* Added work envelope data to [global sys struct](http://svn.io-engineering.com/grblHAL/html/structsystem.html), used by soft limits and jog limit handling. This can be modified by plugin code to restrict motion. + +Drivers: + +* STM32F1xx: added driver support for ganged and auto-squared axes. + +* STM32F4xx: fixed typos. + +--- + Build 20230427 Core: diff --git a/errors.c b/errors.c index d51323f..eaa6978 100644 --- a/errors.c +++ b/errors.c @@ -93,6 +93,7 @@ PROGMEM static const status_detail_t status_detail[] = { #endif { Status_AuthenticationRequired, "Authentication required." }, { Status_AccessDenied, "Access denied." }, + { Status_NotAllowedCriticalEvent, "Not allowed while critical event is active." } }; static error_details_t details = { diff --git a/errors.h b/errors.h index 0f61050..93de049 100644 --- a/errors.h +++ b/errors.h @@ -105,6 +105,7 @@ typedef enum { Status_AuthenticationRequired = 77, Status_AccessDenied = 78, + Status_NotAllowedCriticalEvent = 79, Status_Unhandled, // For internal use only Status_StatusMax = Status_Unhandled diff --git a/grbl.h b/grbl.h index 8b96c93..efb6fef 100644 --- a/grbl.h +++ b/grbl.h @@ -42,7 +42,7 @@ #else #define GRBL_VERSION "1.1f" #endif -#define GRBL_BUILD 20230427 +#define GRBL_BUILD 20230429 #define GRBL_URL "https://github.com/grblHAL" diff --git a/machine_limits.c b/machine_limits.c index 0b5cb99..287e205 100644 --- a/machine_limits.c +++ b/machine_limits.c @@ -109,7 +109,36 @@ ISR_CODE void ISR_FUNC(limit_interrupt_handler)(limit_signals_t state) // DEFAUL } } +// Establish work envelope for homed axes, used by soft limits and jog limits handling. +// When hard limits are enabled pulloff distance is subtracted to avoid triggering limit switches. +void limits_set_work_envelope (void) +{ + uint_fast8_t idx = N_AXIS; + + do { + if(sys.homed.mask & bit(--idx)) { + + float pulloff = settings.limits.flags.hard_enabled && bit_istrue(sys.homing.mask, bit(idx)) ? settings.homing.pulloff : 0.0f; + + if(settings.homing.flags.force_set_origin) { + if(bit_isfalse(settings.homing.dir_mask.value, bit(idx))) { + sys.work_envelope.min[idx] = settings.axis[idx].max_travel + pulloff; + sys.work_envelope.max[idx] = 0.0f; + } else { + sys.work_envelope.min[idx] = 0.0f; + sys.work_envelope.max[idx] = - (settings.axis[idx].max_travel + pulloff); + } + } else { + sys.work_envelope.min[idx] = settings.axis[idx].max_travel + pulloff; + sys.work_envelope.max[idx] = - pulloff; + } + } else + sys.work_envelope.min[idx] = sys.work_envelope.max[idx] = 0.0f; + } while(idx); +} + #ifndef KINEMATICS_API + // Set machine positions for homed limit switches. Don't update non-homed axes. // NOTE: settings.max_travel[] is stored as a negative value. void limits_set_machine_positions (axes_signals_t cycle, bool add_pulloff) @@ -133,6 +162,7 @@ void limits_set_machine_positions (axes_signals_t cycle, bool add_pulloff) } } while(idx); } + #endif // Pulls off axes from asserted homing switches before homing starts. diff --git a/machine_limits.h b/machine_limits.h index e6467ec..a30e44e 100644 --- a/machine_limits.h +++ b/machine_limits.h @@ -3,7 +3,7 @@ Part of grblHAL - Copyright (c) 2017-2022 Terje Io + Copyright (c) 2017-2023 Terje Io Copyright (c) 2012-2015 Sungeun K. Jeon Copyright (c) 2009-2011 Simen Svale Skogsrud @@ -45,6 +45,7 @@ bool limits_homing_required (void); // Set axes to be homed from settings. void limits_set_homing_axes (void); void limits_set_machine_positions (axes_signals_t cycle, bool add_pulloff); +void limits_set_work_envelope (void); void limit_interrupt_handler (limit_signals_t state); diff --git a/motion_control.c b/motion_control.c index 427f6d7..3837bf6 100644 --- a/motion_control.c +++ b/motion_control.c @@ -929,8 +929,13 @@ status_code_t mc_homing_cycle (axes_signals_t cycle) ? Status_LimitsEngaged : Status_OK; - if(homed_status == Status_OK && grbl.on_homing_completed) - grbl.on_homing_completed(); + if(homed_status == Status_OK) { + + limits_set_work_envelope(); + + if(grbl.on_homing_completed) + grbl.on_homing_completed(); + } return homed_status; } diff --git a/protocol.c b/protocol.c index 5d0b740..8bc85ca 100644 --- a/protocol.c +++ b/protocol.c @@ -61,7 +61,7 @@ static char xcommand[LINE_BUFFER_SIZE]; static bool keep_rt_commands = false; static realtime_queue_t realtime_queue = {0}; -static void protocol_exec_rt_suspend (); +static void protocol_exec_rt_suspend (sys_state_t state); static void protocol_execute_rt_commands (void); // add gcode to execute not originating from normal input stream @@ -239,14 +239,14 @@ bool protocol_main_loop (void) // Direct and execute one line of formatted input, and report status of execution. if (line_flags.overflow) // Report line overflow error. gc_state.last_error = Status_Overflow; - else if(line[0] == '\0') // Empty line. For syncing purposes. + else if(*line == '\0') // Empty line. For syncing purposes. gc_state.last_error = Status_OK; - else if (line[0] == '$') {// Grbl '$' system command + else if(*line == '$') {// Grbl '$' system command if((gc_state.last_error = system_execute_line(line)) == Status_LimitsEngaged) { system_raise_alarm(Alarm_LimitsEngaged); grbl.report.feedback_message(Message_CheckLimits); } - } else if (line[0] == '[' && grbl.on_user_command) + } else if(*line == '[' && grbl.on_user_command) gc_state.last_error = grbl.on_user_command(line); else if (state_get() & (STATE_ALARM|STATE_ESTOP|STATE_JOG)) // Everything else is gcode. Block if in alarm, eStop or jog mode. gc_state.last_error = Status_SystemGClock; @@ -276,7 +276,7 @@ bool protocol_main_loop (void) keep_rt_commands = false; char_counter = line_flags.value = 0; - } else if (c <= (char_counter > 0 ? ' ' - 1 : ' ')) + } else if (c != ASCII_BS && c <= (char_counter > 0 ? ' ' - 1 : ' ')) continue; // Strip control characters and leading whitespace. else { switch(c) { @@ -304,6 +304,7 @@ bool protocol_main_loop (void) } break; + case ASCII_BS: case ASCII_DEL: if(char_counter) { line[--char_counter] = '\0'; @@ -388,18 +389,42 @@ bool protocol_execute_realtime (void) { if(protocol_exec_rt_system()) { - if (sys.suspend) - protocol_exec_rt_suspend(); + sys_state_t state = state_get(); - #if NVSDATA_BUFFER_ENABLE - if((state_get() == STATE_IDLE || (state_get() & (STATE_ALARM|STATE_ESTOP))) && settings_dirty.is_dirty && !gc_state.file_run) + if(sys.suspend) + protocol_exec_rt_suspend(state); + +#if NVSDATA_BUFFER_ENABLE + if((state == STATE_IDLE || (state & (STATE_ALARM|STATE_ESTOP))) && settings_dirty.is_dirty && !gc_state.file_run) nvs_buffer_sync_physical(); - #endif +#endif } return !ABORTED; } +static void protocol_poll_cmd (void) +{ + int16_t c; + + if((c = hal.stream.read()) != SERIAL_NO_DATA) { + + if ((c == '\n') || (c == '\r')) { // End of line reached + line[char_counter] = '\0'; + gc_state.last_error = *line == '\0' ? Status_OK : (*line == '$' ? system_execute_line(line) : Status_SystemGClock); + char_counter = 0; + *line = '\0'; + grbl.report.status_message(gc_state.last_error); + } else if(c == ASCII_DEL || c == ASCII_BS) { + if(char_counter) + line[--char_counter] = '\0'; + } else if(char_counter == 0 ? c != ' ' : char_counter < (LINE_BUFFER_SIZE - 1)) + line[char_counter++] = c; + + keep_rt_commands = char_counter > 0 && *line == '$'; + } +} + // Executes run-time commands, when required. This function primarily operates as Grbl's state // machine and controls the various real-time features Grbl has to offer. // NOTE: Do not alter this unless you know exactly what you are doing! @@ -426,11 +451,13 @@ bool protocol_exec_rt_system (void) hal.driver_reset(); // Halt everything upon a critical event flag. Currently hard and soft limits flag this. - if ((alarm_code_t)rt_exec == Alarm_HardLimit || - (alarm_code_t)rt_exec == Alarm_SoftLimit || - (alarm_code_t)rt_exec == Alarm_EStop || - (alarm_code_t)rt_exec == Alarm_MotorFault) { + if((sys.blocking_event = (alarm_code_t)rt_exec == Alarm_HardLimit || + (alarm_code_t)rt_exec == Alarm_SoftLimit || + (alarm_code_t)rt_exec == Alarm_EStop || + (alarm_code_t)rt_exec == Alarm_MotorFault)) { + system_set_exec_alarm(rt_exec); + switch((alarm_code_t)rt_exec) { case Alarm_EStop: @@ -445,20 +472,30 @@ bool protocol_exec_rt_system (void) grbl.report.feedback_message(Message_CriticalEvent); break; } + system_clear_exec_state_flag(EXEC_RESET); // Disable any existing reset + + *line = '\0'; + char_counter = 0; + hal.stream.reset_read_buffer(); + while (bit_isfalse(sys.rt_exec_state, EXEC_RESET)) { + // Block everything, except reset and status reports, until user issues reset or power // cycles. Hard limits typically occur while unattended or not paying attention. Gives // the user and a GUI time to do what is needed before resetting, like killing the // incoming stream. The same could be said about soft limits. While the position is not // lost, continued streaming could cause a serious crash if by chance it gets executed. + if(bit_istrue(sys.rt_exec_state, EXEC_STATUS_REPORT)) { system_clear_exec_state_flag(EXEC_STATUS_REPORT); report_realtime_status(); } + protocol_poll_cmd(); grbl.on_execute_realtime(STATE_ESTOP); } + system_clear_exec_alarm(); // Clear alarm } } @@ -723,13 +760,22 @@ bool protocol_exec_rt_system (void) // whatever function that invoked the suspend, such that Grbl resumes normal operation. // This function is written in a way to promote custom parking motions. Simply use this as a // template. -static void protocol_exec_rt_suspend (void) +static void protocol_exec_rt_suspend (sys_state_t state) { - while (sys.suspend) { + if((sys.blocking_event = state == STATE_SLEEP)) { + *line = '\0'; + char_counter = 0; + hal.stream.reset_read_buffer(); + } - if (sys.abort) + while(sys.suspend) { + + if(sys.abort) return; + if(sys.blocking_event) + protocol_poll_cmd(); + // Handle spindle overrides during suspend state_suspend_manager(); @@ -910,7 +956,7 @@ ISR_CODE bool ISR_FUNC(protocol_enqueue_realtime_command)(char c) break; default: - if(c < ' ' || (c > ASCII_DEL && c <= 0xBF)) + if((c < ' ' && c != ASCII_BS) || (c > ASCII_DEL && c <= 0xBF)) drop = grbl.on_unknown_realtime_cmd == NULL || grbl.on_unknown_realtime_cmd(c); break; } diff --git a/system.c b/system.c index 9b51252..4eb4bb5 100644 --- a/system.c +++ b/system.c @@ -191,79 +191,79 @@ status_code_t read_int (char *s, int32_t *value) } PROGMEM static const sys_command_t sys_commands[] = { - { "G", true, output_parser_state }, - { "J", false, jog }, - { "#", false, output_ngc_parameters }, - { "$", false, output_settings }, - { "+", false, output_all_settings }, + { "G", output_parser_state, { .noargs = On, .allow_blocking = On } }, + { "J", jog }, + { "#", output_ngc_parameters, { .allow_blocking = On } }, + { "$", output_settings, { .allow_blocking = On } }, + { "+", output_all_settings, { .allow_blocking = On } }, #ifndef NO_SETTINGS_DESCRIPTIONS - { "SED", false, output_setting_description }, + { "SED", output_setting_description, { .allow_blocking = On } }, #endif - { "B", true, toggle_block_delete }, - { "S", true, toggle_single_block }, - { "O", true, toggle_optional_stop }, - { "C", true, check_mode }, - { "X", false, disable_lock }, - { "H", false, home }, - { "HX", false, home_x }, - { "HY", false, home_y }, - { "HZ", false, home_z }, + { "B", toggle_block_delete, { .noargs = On } }, + { "S", toggle_single_block, { .noargs = On } }, + { "O", toggle_optional_stop, { .noargs = On } }, + { "C", check_mode, { .noargs = On } }, + { "X", disable_lock }, + { "H", home }, + { "HX", home_x }, + { "HY", home_y }, + { "HZ", home_z }, #if AXIS_REMAP_ABC2UVW #ifdef A_AXIS - { "HU", false, home_a }, + { "HU", home_a }, #endif #ifdef B_AXIS - { "HV", false, home_b }, + { "HV", home_b }, #endif #ifdef C_AXIS - { "HW", false, home_c }, + { "HW", home_c }, #endif #else #ifdef A_AXIS - { "HA", false, home_a }, + { "HA", home_a }, #endif #ifdef B_AXIS - { "HB", false, home_b }, + { "HB", home_b }, #endif #ifdef C_AXIS - { "HC", false, home_c }, + { "HC", home_c }, #endif #endif #ifdef U_AXIS - { "HU", false, home_u }, + { "HU", home_u }, #endif #ifdef V_AXIS - { "HV", false, home_v }, + { "HV", home_v }, #endif - { "HELP", false, output_help }, - { "SPINDLES", false, output_spindles }, - { "SLP", true, enter_sleep }, - { "TLR", true, set_tool_reference }, - { "TPW", true, tool_probe_workpiece }, - { "I", false, build_info }, - { "I+", true, output_all_build_info }, - { "RST", false, settings_reset }, - { "N", true, output_startup_lines }, - { "N0", false, set_startup_line0 }, - { "N1", false, set_startup_line1 }, - { "EA", true, enumerate_alarms }, - { "EAG", true, enumerate_alarms_grblformatted }, - { "EE", true, enumerate_errors }, - { "EEG", true, enumerate_errors_grblformatted }, - { "EG", true, enumerate_groups }, - { "ES", true, enumerate_settings }, - { "ESG", true, enumerate_settings_grblformatted }, - { "ESH", true, enumerate_settings_halformatted }, - { "E*", true, enumerate_all }, - { "PINS", true, enumerate_pins }, - { "RST", false, settings_reset }, - { "LEV", true, report_last_signals_event }, - { "LIM", true, report_current_limit_state }, - { "SD", false, report_spindle_data }, - { "SR", false, spindle_reset_data }, - { "RTC", false, rtc_action }, + { "HELP", output_help, { .allow_blocking = On } }, + { "SPINDLES", output_spindles }, + { "SLP", enter_sleep, { .noargs = On } }, + { "TLR", set_tool_reference, { .noargs = On } }, + { "TPW", tool_probe_workpiece, { .noargs = On } }, + { "I", build_info, { .allow_blocking = On } }, + { "I+", output_all_build_info, { .noargs = On, .allow_blocking = On } }, + { "RST", settings_reset, { .allow_blocking = On } }, + { "N", output_startup_lines, { .noargs = On, .allow_blocking = On } }, + { "N0", set_startup_line0 }, + { "N1", set_startup_line1 }, + { "EA", enumerate_alarms, { .noargs = On, .allow_blocking = On } }, + { "EAG", enumerate_alarms_grblformatted, { .noargs = On, .allow_blocking = On } }, + { "EE", enumerate_errors, { .noargs = On, .allow_blocking = On } }, + { "EEG", enumerate_errors_grblformatted, { .noargs = On, .allow_blocking = On } }, + { "EG", enumerate_groups, { .noargs = On, .allow_blocking = On } }, + { "ES", enumerate_settings, { .noargs = On, .allow_blocking = On } }, + { "ESG", enumerate_settings_grblformatted, { .noargs = On, .allow_blocking = On } }, + { "ESH", enumerate_settings_halformatted, { .noargs = On, .allow_blocking = On } }, + { "E*", enumerate_all, { .noargs = On, .allow_blocking = On } }, + { "PINS", enumerate_pins, { .noargs = On, .allow_blocking = On } }, + { "RST", settings_reset, { .allow_blocking = On } }, + { "LEV", report_last_signals_event, { .noargs = On, .allow_blocking = On } }, + { "LIM", report_current_limit_state, { .noargs = On, .allow_blocking = On } }, + { "SD", report_spindle_data }, + { "SR", spindle_reset_data }, + { "RTC", rtc_action, { .allow_blocking = On } }, #ifdef DEBUGOUT - { "Q", true, output_memmap }, + { "Q", output_memmap, { .noargs = On } }, #endif }; @@ -382,7 +382,10 @@ status_code_t system_execute_line (char *line) do { for(idx = 0; idx < cmd->n_commands; idx++) { if(!strcmp(line, cmd->commands[idx].command)) { - if(!cmd->commands[idx].noargs || args == NULL) { + if(sys.blocking_event && !cmd->commands[idx].flags.allow_blocking) { + retval = Status_NotAllowedCriticalEvent; + break; + } else if(!cmd->commands[idx].flags.noargs || args == NULL) { if((retval = cmd->commands[idx].execute(state_get(), args)) != Status_Unhandled) break; } @@ -801,7 +804,7 @@ static status_code_t output_ngc_parameters (sys_state_t state, char *args) static status_code_t build_info (sys_state_t state, char *args) { - if (!(state == STATE_IDLE || (state & (STATE_ALARM|STATE_ESTOP|STATE_CHECK_MODE)))) + if (!(state == STATE_IDLE || (state & (STATE_ALARM|STATE_ESTOP|STATE_SLEEP|STATE_CHECK_MODE)))) return Status_IdleError; if (args == NULL) { @@ -1003,62 +1006,30 @@ bool system_xy_at_fixture (coord_system_id_t id, float tolerance) return ok; } - // Checks and reports if target array exceeds machine travel limits. Returns false if check failed. -// NOTE: max_travel is stored as negative -// TODO: only check homed axes? bool system_check_travel_limits (float *target) { bool failed = false; uint_fast8_t idx = N_AXIS; - if(settings.homing.flags.force_set_origin) { - do { - idx--; - // When homing forced set origin is enabled, soft limits checks need to account for directionality. - failed = settings.axis[idx].max_travel < -0.0f && - (bit_istrue(settings.homing.dir_mask.value, bit(idx)) - ? (target[idx] < 0.0f || target[idx] > -settings.axis[idx].max_travel) - : (target[idx] > 0.0f || target[idx] < settings.axis[idx].max_travel)); - } while(!failed && idx); - } else do { + if(sys.homed.mask) do { idx--; - failed = settings.axis[idx].max_travel < -0.0f && (target[idx] > 0.0f || target[idx] < settings.axis[idx].max_travel); + if(bit_istrue(sys.homed.mask, bit(idx)) && settings.axis[idx].max_travel < -0.0f) + failed = target[idx] < sys.work_envelope.min[idx] || target[idx] > sys.work_envelope.max[idx]; } while(!failed && idx); return !failed; } // Limits jog commands to be within machine limits, homed axes only. -// When hard limits are enabled pulloff distance is subtracted to avoid triggering limit switches. -// NOTE: max_travel is stored as negative void system_apply_jog_limits (float *target) { uint_fast8_t idx = N_AXIS; if(sys.homed.mask) do { idx--; - float pulloff = settings.limits.flags.hard_enabled && bit_istrue(sys.homing.mask, bit(idx)) ? settings.homing.pulloff : 0.0f; - if(bit_istrue(sys.homed.mask, bit(idx)) && settings.axis[idx].max_travel < -0.0f) { - if(settings.homing.flags.force_set_origin) { - if(bit_isfalse(settings.homing.dir_mask.value, bit(idx))) { - if(target[idx] > 0.0f) - target[idx] = 0.0f; - else if(target[idx] < (settings.axis[idx].max_travel + pulloff)) - target[idx] = (settings.axis[idx].max_travel + pulloff); - } else { - if(target[idx] < 0.0f) - target[idx] = 0.0f; - else if(target[idx] > -(settings.axis[idx].max_travel + pulloff)) - target[idx] = -(settings.axis[idx].max_travel + pulloff); - } - } else { - if(target[idx] > -pulloff) - target[idx] = -pulloff; - else if(target[idx] < (settings.axis[idx].max_travel + pulloff)) - target[idx] = (settings.axis[idx].max_travel + pulloff); - } - } + if(bit_istrue(sys.homed.mask, bit(idx)) && settings.axis[idx].max_travel < -0.0f) + target[idx] = max(min(target[idx], sys.work_envelope.max[idx]), sys.work_envelope.min[idx]); } while(idx); } diff --git a/system.h b/system.h index 24ff621..3406148 100644 --- a/system.h +++ b/system.h @@ -222,10 +222,10 @@ typedef struct { typedef union { uint8_t flags; struct { - uint16_t feedrate :1, - coolant :1, - spindle :1, - unused :5; + uint8_t feedrate :1, + coolant :1, + spindle :1, + unused :5; }; } system_override_delay_t; @@ -246,12 +246,16 @@ typedef union { }; } system_flags_t; -typedef struct -{ +typedef struct { control_signals_t control; limit_signals_t limits; } signal_event_t; +typedef struct { + float min[N_AXIS]; + float max[N_AXIS]; +} work_envelope_t; + //! Global system variables struct. // NOTE: probe_position and position variables may need to be declared as volatiles, if problems arise. typedef struct system { @@ -260,6 +264,7 @@ typedef struct system { bool suspend; //!< System suspend state flag. bool position_lost; //!< Set when mc_reset is called when machine is moving. bool reset_pending; //!< Set when reset processing is underway. + bool blocking_event; //!< Set when a blocking event that requires reset to clear is active. volatile bool steppers_deenergize; //!< Set to true to deenergize stepperes axes_signals_t tlo_reference_set; //!< Axes with tool length reference offset set int32_t tlo_reference[N_AXIS]; //!< Tool length reference offset @@ -284,7 +289,8 @@ typedef struct system { //! @name The following variables are only cleared upon soft reset if position is likely lost, do NOT move. homed must be first! //@{ axes_signals_t homed; //!< Indicates which axes has been homed. - float home_position[N_AXIS]; //!< Home position for homed axes + float home_position[N_AXIS]; //!< Home position for homed axes. + work_envelope_t work_envelope; //!< Work envelope, only valid for homed axes. //@} //! @name The following variables are not cleared upon soft reset, do NOT move. alarm must be first! //@{ @@ -299,11 +305,20 @@ typedef struct system { typedef status_code_t (*sys_command_ptr)(sys_state_t state, char *args); +typedef union { + uint8_t flags; + struct { + uint8_t noargs :1, //!< System command does not handle arguments. + allow_blocking :1, //!< System command can be used when blocking event is active. + unused :6; + }; +} sys_command_flags_t; + typedef struct { const char *command; - bool noargs; sys_command_ptr execute; + sys_command_flags_t flags; } sys_command_t; typedef struct sys_commands_str {