mirror of
https://github.com/grblHAL/core.git
synced 2026-02-06 00:52:35 +08:00
Added experimental support for G66 (modal macro call) and G67 (end modal macro call). Made axis letter to axis/motor assignment for axes ABCUVW freely changeable at compile time. Fix for some G65 arguments being incorrectly validated for normal use (sign, range). Added repeat support to G65 macro call via the optional L parameter word. Changed default setting for ABC-axes to rotary. Changed defaults for jerk settings to 10x acceleration settings. Disabled jerk for jog, probe and spindle synchronized motion. Added _active_probe system parameter, returns -1 if no probe inputs available. Minor bug fix, G5.1 and G33.1 motion commands were not coverted to the correct string equivalent in $G output.
1031 lines
39 KiB
C
1031 lines
39 KiB
C
/*
|
|
protocol.c - controls grblHAL execution protocol and procedures
|
|
|
|
Part of grblHAL
|
|
|
|
Copyright (c) 2017-2026 Terje Io
|
|
Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC
|
|
Copyright (c) 2009-2011 Simen Svale Skogsrud
|
|
|
|
grblHAL is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
grblHAL is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "hal.h"
|
|
#include "nuts_bolts.h"
|
|
#include "nvs_buffer.h"
|
|
#include "override.h"
|
|
#include "state_machine.h"
|
|
#include "motion_control.h"
|
|
#include "sleep.h"
|
|
#include "protocol.h"
|
|
#include "machine_limits.h"
|
|
|
|
#ifndef RT_QUEUE_SIZE
|
|
#define RT_QUEUE_SIZE 16 // must be a power of 2
|
|
#endif
|
|
|
|
// Define line flags. Includes comment type tracking and line overflow detection.
|
|
typedef union {
|
|
uint8_t value;
|
|
struct {
|
|
uint8_t overflow :1,
|
|
comment_parentheses :1,
|
|
comment_semicolon :1,
|
|
unassigned :5;
|
|
};
|
|
} line_flags_t;
|
|
|
|
extern void task_execute_on_startup (void);
|
|
|
|
static uint_fast16_t char_counter = 0;
|
|
static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated.
|
|
static char xcommand[LINE_BUFFER_SIZE];
|
|
static bool keep_rt_commands = false;
|
|
|
|
static void protocol_exec_rt_suspend (sys_state_t state);
|
|
|
|
// add gcode to execute not originating from normal input stream
|
|
bool protocol_enqueue_gcode (char *gcode)
|
|
{
|
|
bool ok = xcommand[0] == '\0' &&
|
|
(state_get() == STATE_IDLE || (state_get() & (STATE_ALARM|STATE_JOG|STATE_TOOL_CHANGE))) &&
|
|
!((sys.rt_exec_state & (EXEC_MOTION_CANCEL|EXEC_MOTION_CANCEL_FAST)));
|
|
|
|
if(ok && gc_state.file_run)
|
|
ok = gc_state.modal.program_flow != ProgramFlow_Running || strncmp((char *)gcode, "$J=", 3);
|
|
|
|
if(ok)
|
|
strcpy(xcommand, gcode);
|
|
|
|
return ok;
|
|
}
|
|
|
|
static bool recheck_line (char *line, line_flags_t *flags)
|
|
{
|
|
bool keep_rt_commands = false, first_char = true;
|
|
|
|
flags->value = 0;
|
|
|
|
if(*line != '\0') do {
|
|
|
|
switch(*line) {
|
|
|
|
case '$':
|
|
case '[':
|
|
if(first_char)
|
|
keep_rt_commands = true;
|
|
break;
|
|
|
|
case '(':
|
|
if(!keep_rt_commands && (flags->comment_parentheses = !flags->comment_semicolon))
|
|
keep_rt_commands = !hal.driver_cap.no_gcode_message_handling; // Suspend real-time processing of printable command characters.
|
|
break;
|
|
|
|
case ')':
|
|
if(!flags->comment_semicolon)
|
|
flags->comment_parentheses = keep_rt_commands = false;
|
|
break;
|
|
|
|
case ';':
|
|
if(!flags->comment_parentheses) {
|
|
keep_rt_commands = false;
|
|
flags->comment_semicolon = On;
|
|
}
|
|
break;
|
|
}
|
|
|
|
first_char = false;
|
|
|
|
} while(*++line != '\0');
|
|
|
|
return keep_rt_commands;
|
|
}
|
|
|
|
/*
|
|
grblHAL PRIMARY LOOP:
|
|
*/
|
|
bool protocol_main_loop (void)
|
|
{
|
|
if(sys.alarm == Alarm_SelftestFailed) {
|
|
sys.alarm = Alarm_None;
|
|
system_raise_alarm(Alarm_SelftestFailed);
|
|
} else if (hal.control.get_state().e_stop) {
|
|
// Check for e-stop active. Blocks everything until cleared.
|
|
system_raise_alarm(Alarm_EStop);
|
|
grbl.report.feedback_message(Message_EStop);
|
|
} else if(hal.control.get_state().motor_fault) {
|
|
// Check for motor fault active. Blocks everything until cleared.
|
|
system_raise_alarm(Alarm_MotorFault);
|
|
grbl.report.feedback_message(Message_MotorFault);
|
|
} else if(settings.probe.enable_protection && hal.control.get_state().probe_triggered) {
|
|
system_raise_alarm(Alarm_ProbeProtect);
|
|
grbl.report.feedback_message(Message_ProbeProtected);
|
|
} else if (limits_homing_required()) {
|
|
// Check for power-up and set system alarm if homing is enabled to force homing cycle
|
|
// by setting grblHAL's alarm state. Alarm locks out all g-code commands, including the
|
|
// startup scripts, but allows access to settings and internal commands.
|
|
// Only a successful homing cycle '$H' will disable the alarm.
|
|
// NOTE: The startup script will run after successful completion of the homing cycle. Prevents motion startup
|
|
// blocks from crashing into things uncontrollably. Very bad.
|
|
system_raise_alarm(Alarm_HomingRequired);
|
|
grbl.report.feedback_message(Message_HomingCycleRequired);
|
|
} else if (settings.limits.flags.hard_enabled &&
|
|
settings.limits.flags.check_at_init &&
|
|
(limit_signals_merge(hal.limits.get_state()).value & sys.hard_limits.mask)) {
|
|
if(sys.alarm == Alarm_LimitsEngaged && hal.control.get_state().limits_override)
|
|
state_set(STATE_IDLE); // Clear alarm state to enable limit switch pulloff.
|
|
else {
|
|
// Check that no limit switches are engaged to make sure everything is good to go.
|
|
system_raise_alarm(Alarm_LimitsEngaged);
|
|
grbl.report.feedback_message(Message_CheckLimits);
|
|
}
|
|
} else if(sys.cold_start && (settings.flags.force_initialization_alarm || hal.control.get_state().reset)) {
|
|
state_set(STATE_ALARM); // Ensure alarm state is set.
|
|
grbl.report.feedback_message(Message_AlarmLock);
|
|
} else if (state_get() & (STATE_ALARM|STATE_SLEEP)) {
|
|
// Check for and report alarm state after a reset, error, or an initial power up.
|
|
// NOTE: Sleep mode disables the stepper drivers and position can't be guaranteed.
|
|
// Re-initialize the sleep state as an ALARM mode to ensure user homes or acknowledges.
|
|
if(sys.alarm == Alarm_HomingRequired)
|
|
sys.alarm = Alarm_None; // Clear Alarm_HomingRequired as the lock has been overridden by a soft reset.
|
|
state_set(STATE_ALARM); // Ensure alarm state is set.
|
|
grbl.report.feedback_message(Message_AlarmLock);
|
|
} else {
|
|
state_set(STATE_IDLE);
|
|
#ifndef NO_SAFETY_DOOR_SUPPORT
|
|
// Check if the safety door is open.
|
|
if (hal.signals_cap.safety_door_ajar && !settings.safety_door.flags.ignore_when_idle && hal.control.get_state().safety_door_ajar) {
|
|
system_set_exec_state_flag(EXEC_SAFETY_DOOR);
|
|
protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state.
|
|
}
|
|
#endif
|
|
// All systems go!
|
|
if(!settings.homing.flags.nx_scrips_on_homed_only)
|
|
task_add_immediate(system_execute_startup, NULL); // Schedule startup script for execution.
|
|
}
|
|
|
|
// Ensure spindle and coolant is switched off on a cold start
|
|
if(sys.cold_start) {
|
|
spindle_all_off();
|
|
hal.coolant.set_state((coolant_state_t){0});
|
|
sys.cold_start = false;
|
|
system_set_exec_state_flag(EXEC_RT_COMMAND); // execute any startup up tasks
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// Primary loop! Upon a system abort, this exits back to main() to reset the system.
|
|
// This is also where grblHAL idles while waiting for something to do.
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
int32_t c;
|
|
char eol = '\0';
|
|
line_flags_t line_flags = {0};
|
|
|
|
xcommand[0] = '\0';
|
|
char_counter = 0;
|
|
keep_rt_commands = false;
|
|
|
|
while(true) {
|
|
|
|
// Process one line of incoming stream data, as the data becomes available. Performs an
|
|
// initial filtering by removing leading spaces and control characters.
|
|
while((c = hal.stream.read()) != SERIAL_NO_DATA) {
|
|
|
|
if(c == ASCII_CAN) {
|
|
|
|
eol = xcommand[0] = '\0';
|
|
keep_rt_commands = false;
|
|
char_counter = line_flags.value = 0;
|
|
gc_state.last_error = Status_OK;
|
|
|
|
if (state_get() == STATE_JOG) // Block all other states from invoking motion cancel.
|
|
system_set_exec_state_flag(EXEC_MOTION_CANCEL);
|
|
|
|
} else if(c == ASCII_EOF) {
|
|
if(grbl.on_file_end)
|
|
grbl.on_file_end(hal.stream.file, gc_state.last_error);
|
|
} else if ((c == '\n') || (c == '\r')) { // End of line reached
|
|
|
|
// Check for possible secondary end of line character, do not process as empty line
|
|
// if part of crlf (or lfcr pair) as this produces a possibly unwanted double response
|
|
if(char_counter == 0 && eol && eol != c) {
|
|
eol = '\0';
|
|
continue;
|
|
} else
|
|
eol = (char)c;
|
|
|
|
if(!protocol_execute_realtime()) // Runtime command check point.
|
|
return !sys.flags.exit; // Bail to calling function upon system abort
|
|
|
|
line[char_counter] = '\0'; // Set string termination character.
|
|
|
|
#if REPORT_ECHO_LINE_RECEIVED
|
|
report_echo_line_received(line);
|
|
#endif
|
|
|
|
// 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') // Empty line. For syncing purposes.
|
|
gc_state.last_error = Status_OK;
|
|
else if(*line == '$') {// grblHAL '$' 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 == '[' && 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.
|
|
if(*line == CMD_PROGRAM_DEMARCATION && line[1] == '\0' && (state_get() & (STATE_ALARM|STATE_ESTOP))) {
|
|
gc_state.file_run = !gc_state.file_run;
|
|
gc_state.last_error = Status_OK;
|
|
if(grbl.on_file_demarcate)
|
|
grbl.on_file_demarcate(gc_state.file_run);
|
|
} else
|
|
gc_state.last_error = Status_SystemGClock;
|
|
}
|
|
#if COMPATIBILITY_LEVEL == 0
|
|
else if(gc_state.last_error == Status_OK || gc_state.last_error == Status_GcodeToolChangePending) { // Parse and execute g-code block.
|
|
#else
|
|
else { // Parse and execute g-code block.
|
|
|
|
#endif
|
|
if((gc_state.last_error = gc_execute_block(line)) != Status_OK)
|
|
eol = '\0';
|
|
}
|
|
|
|
// Add a short delay for each block processed in Check Mode to
|
|
// avoid overwhelming the sender with fast reply messages.
|
|
// This is likely to happen when streaming is done via a protocol where
|
|
// the speed is not limited to 115200 baud. An example is native USB streaming.
|
|
#if CHECK_MODE_DELAY
|
|
if(state_get() == STATE_CHECK_MODE)
|
|
hal.delay_ms(CHECK_MODE_DELAY, NULL);
|
|
#endif
|
|
if(ABORTED)
|
|
break;
|
|
else
|
|
grbl.report.status_message(gc_state.last_error);
|
|
|
|
// Reset tracking data for next line.
|
|
keep_rt_commands = false;
|
|
char_counter = line_flags.value = 0;
|
|
|
|
} else if (c != ASCII_BS && c <= (char_counter > 0 ? ' ' - 1 : ' '))
|
|
continue; // Strip control characters and leading whitespace.
|
|
else {
|
|
switch(c) {
|
|
|
|
case '$':
|
|
case '[':
|
|
if(char_counter == 0)
|
|
keep_rt_commands = true;
|
|
break;
|
|
|
|
case '(':
|
|
if(!keep_rt_commands && (line_flags.comment_parentheses = !line_flags.comment_semicolon))
|
|
keep_rt_commands = !hal.driver_cap.no_gcode_message_handling; // Suspend real-time processing of printable command characters.
|
|
break;
|
|
|
|
case ')':
|
|
if(!line_flags.comment_semicolon)
|
|
line_flags.comment_parentheses = keep_rt_commands = false;
|
|
break;
|
|
|
|
case ';':
|
|
if(!line_flags.comment_parentheses) {
|
|
keep_rt_commands = false;
|
|
line_flags.comment_semicolon = On;
|
|
}
|
|
break;
|
|
|
|
case ASCII_BS:
|
|
case ASCII_DEL:
|
|
if(char_counter) {
|
|
line[--char_counter] = '\0';
|
|
keep_rt_commands = recheck_line(line, &line_flags);
|
|
}
|
|
continue;
|
|
}
|
|
if(!(line_flags.overflow = char_counter >= (LINE_BUFFER_SIZE - 1)))
|
|
line[char_counter++] = c;
|
|
}
|
|
}
|
|
|
|
// Handle extra command (internal stream)
|
|
if(xcommand[0] != '\0') {
|
|
|
|
if (xcommand[0] == '$') // grblHAL '$' system command
|
|
system_execute_line(xcommand);
|
|
else if (state_get() & (STATE_ALARM|STATE_ESTOP|STATE_JOG)) // Everything else is gcode. Block if in alarm, eStop or jog state.
|
|
grbl.report.status_message(Status_SystemGClock);
|
|
else // Parse and execute g-code block.
|
|
gc_execute_block(xcommand);
|
|
|
|
xcommand[0] = '\0';
|
|
}
|
|
|
|
// If there are no more characters in the input stream buffer to be processed and executed,
|
|
// this indicates that g-code streaming has either filled the planner buffer or has
|
|
// completed. In either case, auto-cycle start, if enabled, any queued moves.
|
|
protocol_auto_cycle_start();
|
|
|
|
if(!protocol_execute_realtime() && sys.abort) // Runtime command check point.
|
|
return !sys.flags.exit; // Bail to main() program loop to reset system.
|
|
|
|
sys.cancel = false;
|
|
|
|
// Check for sleep conditions and execute auto-park, if timeout duration elapses.
|
|
if(settings.flags.sleep_enable)
|
|
sleep_check();
|
|
}
|
|
}
|
|
|
|
|
|
// Block until all buffered steps are executed or in a cycle state. Works with feed hold
|
|
// during a synchronize call, if it should happen. Also, waits for clean cycle end.
|
|
bool protocol_buffer_synchronize (void)
|
|
{
|
|
bool ok = true;
|
|
|
|
// If system is queued, ensure cycle resumes if the auto start flag is present.
|
|
protocol_auto_cycle_start();
|
|
|
|
while((ok = protocol_execute_realtime()) && (plan_get_current_block() || state_get() == STATE_CYCLE));
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
// Auto-cycle start triggers when there is a motion ready to execute and if the main program is not
|
|
// actively parsing commands.
|
|
// NOTE: This function is called from the main loop, buffer sync, and mc_line() only and executes
|
|
// when one of these conditions exist respectively: There are no more blocks sent (i.e. streaming
|
|
// is finished, single commands), a command that needs to wait for the motions in the buffer to
|
|
// execute calls a buffer sync, or the planner buffer is full and ready to go.
|
|
void protocol_auto_cycle_start (void)
|
|
{
|
|
if(!ABORTED && plan_get_current_block()) // Check if there are any blocks in the buffer.
|
|
system_set_exec_state_flag(EXEC_CYCLE_START); // If so, execute them!
|
|
}
|
|
|
|
|
|
// This function is the general interface to grblHAL's real-time command execution system. It is called
|
|
// from various check points in the main program, primarily where there may be a while loop waiting
|
|
// for a buffer to clear space or any point where the execution time from the last check point may
|
|
// be more than a fraction of a second. This is a way to execute realtime commands asynchronously
|
|
// (aka multitasking) with grblHAL's g-code parsing and planning functions. This function also serves
|
|
// as an interface for the interrupts to set the system realtime flags, where only the main program
|
|
// handles them, removing the need to define more computationally-expensive volatile variables. This
|
|
// also provides a controlled way to execute certain tasks without having two or more instances of
|
|
// the same task, such as the planner recalculating the buffer upon a feedhold or overrides.
|
|
// NOTE: The sys_rt_exec_state variable flags are set by any process, step or input stream events, pinouts,
|
|
// limit switches, or the main program.
|
|
// Returns false if aborted
|
|
bool protocol_execute_realtime (void)
|
|
{
|
|
if(protocol_exec_rt_system()) {
|
|
|
|
sys_state_t state = state_get();
|
|
|
|
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
|
|
}
|
|
|
|
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 grblHAL's state
|
|
// machine and controls the various real-time features grblHAL has to offer.
|
|
// NOTE: Do not alter this unless you know exactly what you are doing!
|
|
bool protocol_exec_rt_system (void)
|
|
{
|
|
rt_exec_t rt_exec;
|
|
bool killed = false;
|
|
|
|
if (sys.rt_exec_alarm && (rt_exec = system_clear_exec_alarm())) { // Enter only if any bit flag is true
|
|
|
|
if((sys.reset_pending = bit_istrue(sys.rt_exec_state, EXEC_RESET))) {
|
|
// Kill spindle and coolant.
|
|
killed = true;
|
|
spindle_all_off();
|
|
hal.coolant.set_state((coolant_state_t){0});
|
|
}
|
|
|
|
// System alarm. Everything has shutdown by something that has gone severely wrong. Report
|
|
// the source of the error to the user. If critical, grblHAL disables by entering an infinite
|
|
// loop until system reset/abort.
|
|
system_raise_alarm((alarm_code_t)rt_exec);
|
|
|
|
if(killed) // Tell driver/plugins about reset.
|
|
hal.driver_reset();
|
|
|
|
// Halt everything upon a critical event flag. Currently hard and soft limits flag this.
|
|
if((sys.blocking_event = alarm_is_critical((alarm_code_t)rt_exec))) {
|
|
|
|
static const control_signals_t blocking_signals = { .e_stop = On, .motor_fault = On };
|
|
|
|
system_set_exec_alarm(rt_exec);
|
|
|
|
switch((alarm_code_t)rt_exec) {
|
|
|
|
case Alarm_EStop:
|
|
grbl.report.feedback_message(Message_EStop);
|
|
break;
|
|
|
|
case Alarm_MotorFault:
|
|
grbl.report.feedback_message(Message_MotorFault);
|
|
break;
|
|
|
|
default:
|
|
grbl.report.feedback_message(Message_CriticalEvent);
|
|
break;
|
|
}
|
|
|
|
*line = '\0';
|
|
char_counter = 0;
|
|
hal.stream.reset_read_buffer();
|
|
|
|
system_clear_exec_state_flag(EXEC_RESET); // Disable any existing reset
|
|
|
|
while(!(sys.abort = bit_istrue(sys.rt_exec_state, EXEC_RESET)) || (hal.control.get_state().bits & blocking_signals.bits)) {
|
|
|
|
// 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(hal.stream.write_all, system_get_rt_report_flags());
|
|
}
|
|
|
|
protocol_poll_cmd();
|
|
grbl.on_execute_realtime(STATE_ESTOP);
|
|
}
|
|
|
|
system_clear_exec_alarm(); // Clear alarm
|
|
|
|
return false; // Nothing else to do but exit.
|
|
}
|
|
}
|
|
|
|
if (sys.rt_exec_state && (rt_exec = system_clear_exec_states())) { // Get and clear volatile sys.rt_exec_state atomically.
|
|
|
|
// Execute system abort.
|
|
if((sys.reset_pending = bit_istrue(rt_exec, EXEC_RESET))) {
|
|
|
|
if(!killed) {
|
|
// Kill spindle and coolant.
|
|
spindle_all_off();
|
|
hal.coolant.set_state((coolant_state_t){0});
|
|
}
|
|
|
|
// Only place sys.abort is set true, when E-stop is not asserted.
|
|
if(!(sys.abort = !hal.control.get_state().e_stop)) {
|
|
hal.stream.reset_read_buffer();
|
|
system_raise_alarm(Alarm_EStop);
|
|
grbl.report.feedback_message(Message_EStop);
|
|
} else if(hal.control.get_state().motor_fault) {
|
|
sys.abort = false;
|
|
hal.stream.reset_read_buffer();
|
|
system_raise_alarm(Alarm_MotorFault);
|
|
grbl.report.feedback_message(Message_MotorFault);
|
|
}
|
|
|
|
if(!killed) // Tell driver/plugins about reset.
|
|
hal.driver_reset();
|
|
|
|
return !sys.abort; // Nothing else to do but exit.
|
|
}
|
|
|
|
if(rt_exec & EXEC_STOP) { // Experimental for now, must be verified. Do NOT move to interrupt context!
|
|
|
|
// Note: homing cannot be cancelled with EXEC_STOP
|
|
|
|
sys.cancel = true;
|
|
sys.step_control.flags = 0;
|
|
sys.flags.feed_hold_pending = Off;
|
|
sys.override_delay.flags = 0;
|
|
if(sys.override.control.sync)
|
|
sys.override.control = gc_state.modal.override_ctrl;
|
|
|
|
gc_state.tool_change = false;
|
|
|
|
// Tell driver/plugins about reset.
|
|
hal.driver_reset();
|
|
|
|
if(!sys.flags.keep_input && hal.stream.suspend_read && hal.stream.suspend_read(false))
|
|
hal.stream.cancel_read_buffer(); // flush pending blocks (after M6)
|
|
|
|
sys.flags.keep_input = Off;
|
|
|
|
if(sys.alarm_pending != Alarm_None) {
|
|
|
|
sys.position_lost = st_is_stepping();
|
|
system_raise_alarm(sys.alarm_pending);
|
|
sys.alarm_pending = Alarm_None;
|
|
|
|
} else if(st_is_stepping()) {
|
|
|
|
state_update(EXEC_MOTION_CANCEL_FAST);
|
|
|
|
do {
|
|
st_prep_buffer(); // Check and prep segment buffer.
|
|
grbl.on_execute_realtime(state_get());
|
|
} while(st_is_stepping());
|
|
|
|
rt_exec |= system_clear_exec_states();
|
|
}
|
|
|
|
gc_init(true);
|
|
plan_reset();
|
|
st_reset();
|
|
sync_position();
|
|
|
|
// Kill spindle and coolant. TODO: Check Mach3 behaviour?
|
|
gc_spindle_off();
|
|
gc_coolant((coolant_state_t){0});
|
|
|
|
flush_override_buffers();
|
|
|
|
if(!((state_get() == STATE_ALARM) && (sys.alarm == Alarm_LimitsEngaged || sys.alarm == Alarm_HomingRequired || sys.alarm == Alarm_ProbeProtect))) {
|
|
state_set(hal.control.get_state().safety_door_ajar ? STATE_SAFETY_DOOR : STATE_IDLE);
|
|
grbl.report.feedback_message(Message_Stop);
|
|
}
|
|
}
|
|
|
|
// Execute and print status to output stream
|
|
if (rt_exec & EXEC_STATUS_REPORT)
|
|
report_realtime_status(hal.stream.write_all, system_get_rt_report_flags());
|
|
|
|
if(rt_exec & EXEC_GCODE_REPORT)
|
|
report_gcode_modes(hal.stream.write);
|
|
|
|
if(rt_exec & EXEC_TLO_REPORT)
|
|
report_tool_offsets();
|
|
|
|
// Execute and print PID log to output stream
|
|
if (rt_exec & EXEC_PID_REPORT)
|
|
report_pid_log();
|
|
|
|
if(rt_exec & EXEC_RT_COMMAND)
|
|
task_execute_on_startup();
|
|
|
|
rt_exec &= ~(EXEC_STOP|EXEC_STATUS_REPORT|EXEC_GCODE_REPORT|EXEC_PID_REPORT|EXEC_TLO_REPORT|EXEC_RT_COMMAND); // clear requests already processed
|
|
|
|
// Let state machine handle any remaining requests
|
|
if(rt_exec)
|
|
state_update(rt_exec);
|
|
}
|
|
|
|
grbl.on_execute_realtime(state_get());
|
|
|
|
// Execute overrides.
|
|
|
|
if(!sys.override_delay.feedrate && (rt_exec = get_feed_override())) {
|
|
|
|
override_t new_f_override = sys.override.feed_rate;
|
|
override_t new_r_override = sys.override.rapid_rate;
|
|
|
|
do {
|
|
|
|
switch(rt_exec) {
|
|
|
|
case CMD_OVERRIDE_FEED_RESET:
|
|
new_f_override = DEFAULT_FEED_OVERRIDE;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_FEED_COARSE_PLUS:
|
|
new_f_override += FEED_OVERRIDE_COARSE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_FEED_COARSE_MINUS:
|
|
new_f_override -= FEED_OVERRIDE_COARSE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_FEED_FINE_PLUS:
|
|
new_f_override += FEED_OVERRIDE_FINE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_FEED_FINE_MINUS:
|
|
new_f_override -= FEED_OVERRIDE_FINE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_RAPID_RESET:
|
|
new_r_override = DEFAULT_RAPID_OVERRIDE;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_RAPID_MEDIUM:
|
|
new_r_override = RAPID_OVERRIDE_MEDIUM;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_RAPID_LOW:
|
|
new_r_override = RAPID_OVERRIDE_LOW;
|
|
break;
|
|
}
|
|
|
|
new_f_override = constrain(new_f_override, MIN_FEED_RATE_OVERRIDE, MAX_FEED_RATE_OVERRIDE);
|
|
|
|
} while((rt_exec = get_feed_override()));
|
|
|
|
plan_feed_override(new_f_override, new_r_override);
|
|
}
|
|
|
|
if(!sys.override_delay.spindle && (rt_exec = get_spindle_override())) {
|
|
|
|
bool spindle_stop = false;
|
|
spindle_ptrs_t *spindle = gc_spindle_get(-1)->hal;
|
|
override_t last_s_override = spindle->param->override_pct;
|
|
|
|
do {
|
|
|
|
switch(rt_exec) {
|
|
|
|
case CMD_OVERRIDE_SPINDLE_RESET:
|
|
last_s_override = DEFAULT_SPINDLE_RPM_OVERRIDE;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_SPINDLE_COARSE_PLUS:
|
|
last_s_override += SPINDLE_OVERRIDE_COARSE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_SPINDLE_COARSE_MINUS:
|
|
last_s_override -= SPINDLE_OVERRIDE_COARSE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_SPINDLE_FINE_PLUS:
|
|
last_s_override += SPINDLE_OVERRIDE_FINE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_SPINDLE_FINE_MINUS:
|
|
last_s_override -= SPINDLE_OVERRIDE_FINE_INCREMENT;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_SPINDLE_STOP:
|
|
spindle_stop = !spindle_stop;
|
|
break;
|
|
|
|
default:
|
|
if(grbl.on_unknown_accessory_override)
|
|
grbl.on_unknown_accessory_override(rt_exec);
|
|
break;
|
|
}
|
|
|
|
last_s_override = constrain(last_s_override, MIN_SPINDLE_RPM_OVERRIDE, MAX_SPINDLE_RPM_OVERRIDE);
|
|
|
|
} while((rt_exec = get_spindle_override()));
|
|
|
|
spindle_set_override(spindle, last_s_override);
|
|
|
|
if (spindle_stop && state_get() == STATE_HOLD && gc_spindle_get(-1)->state.on) {
|
|
// Spindle stop override allowed only while in HOLD state.
|
|
// NOTE: Report flag is set in spindle_set_state() when spindle stop is executed.
|
|
if (!sys.override.spindle_stop.value)
|
|
sys.override.spindle_stop.initiate = On;
|
|
else if (sys.override.spindle_stop.enabled)
|
|
sys.override.spindle_stop.restore = On;
|
|
}
|
|
}
|
|
|
|
if(!sys.override_delay.coolant && (rt_exec = get_coolant_override())) {
|
|
|
|
coolant_state_t coolant_state = gc_state.modal.coolant;
|
|
|
|
do {
|
|
|
|
switch(rt_exec) {
|
|
|
|
case CMD_OVERRIDE_COOLANT_MIST_TOGGLE:
|
|
if(hal.coolant_cap.mist && ((state_get() == STATE_IDLE) || (state_get() & (STATE_CYCLE | STATE_HOLD))))
|
|
coolant_state.mist = !coolant_state.mist;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_COOLANT_FLOOD_TOGGLE:
|
|
if(hal.coolant_cap.flood && ((state_get() == STATE_IDLE) || (state_get() & (STATE_CYCLE | STATE_HOLD))))
|
|
coolant_state.flood = !coolant_state.flood;
|
|
break;
|
|
|
|
default:
|
|
if(grbl.on_unknown_accessory_override)
|
|
grbl.on_unknown_accessory_override(rt_exec);
|
|
break;
|
|
}
|
|
|
|
} while((rt_exec = get_coolant_override()));
|
|
|
|
// NOTE: Since coolant state always performs a planner sync whenever it changes, the current
|
|
// run state can be determined by checking the parser state.
|
|
if(coolant_state.value != gc_state.modal.coolant.value) {
|
|
gc_coolant(coolant_state); // Report flag set in gc_coolant().
|
|
if(grbl.on_override_changed)
|
|
grbl.on_override_changed(OverrideChanged_CoolantState);
|
|
}
|
|
}
|
|
|
|
// End execute overrides.
|
|
|
|
// Reload step segment buffer
|
|
if (state_get() & (STATE_CYCLE | STATE_HOLD | STATE_SAFETY_DOOR | STATE_HOMING | STATE_SLEEP| STATE_JOG))
|
|
st_prep_buffer();
|
|
|
|
return !ABORTED;
|
|
}
|
|
|
|
// Handles grblHAL system suspend procedures, such as feed hold, safety door, and parking motion.
|
|
// The system will enter this loop, create local variables for suspend tasks, and return to
|
|
// whatever function that invoked the suspend, such that grblHAL 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 (sys_state_t state)
|
|
{
|
|
if((sys.blocking_event = state == STATE_SLEEP)) {
|
|
*line = '\0';
|
|
char_counter = 0;
|
|
hal.stream.reset_read_buffer();
|
|
}
|
|
|
|
while(sys.suspend) {
|
|
|
|
if(sys.abort)
|
|
return;
|
|
|
|
if(sys.blocking_event)
|
|
protocol_poll_cmd();
|
|
|
|
// Handle spindle overrides during suspend
|
|
state_suspend_manager();
|
|
|
|
// If door closed keep issuing door closed requests until resumed
|
|
if(state_get() == STATE_SAFETY_DOOR && !hal.control.get_state().safety_door_ajar)
|
|
system_set_exec_state_flag(EXEC_DOOR_CLOSED);
|
|
|
|
// Check for sleep conditions and execute auto-park, if timeout duration elapses.
|
|
// Sleep is valid for both hold and door states, if the spindle or coolant are on or
|
|
// set to be re-enabled.
|
|
if(settings.flags.sleep_enable)
|
|
sleep_check();
|
|
|
|
protocol_exec_rt_system();
|
|
}
|
|
}
|
|
|
|
// Pick off (drop) real-time command characters from input stream.
|
|
// These characters are not passed into the main buffer,
|
|
// but rather sets system state flag bits for later execution by protocol_exec_rt_system().
|
|
// Called from input stream interrupt handler.
|
|
ISR_CODE bool ISR_FUNC(protocol_enqueue_realtime_command)(uint8_t c)
|
|
{
|
|
static bool esc = false;
|
|
|
|
bool drop = false;
|
|
control_signals_t signals = {};
|
|
|
|
// 1. Process characters in the ranges 0x - 1x and 8x-Ax
|
|
// Characters with functions assigned are always acted upon even when the input stream
|
|
// is redirected to a non-interactive stream such as from a SD card.
|
|
|
|
switch ((unsigned char)c) {
|
|
|
|
case '\n':
|
|
case '\r':
|
|
break;
|
|
|
|
case '$':
|
|
if(char_counter == 0)
|
|
keep_rt_commands = !settings.flags.legacy_rt_commands;
|
|
break;
|
|
|
|
case CMD_STOP:
|
|
system_set_exec_state_flag(EXEC_STOP);
|
|
char_counter = 0;
|
|
hal.stream.cancel_read_buffer();
|
|
drop = true;
|
|
break;
|
|
|
|
case CMD_RESET: // Call motion control reset routine.
|
|
if(!hal.control.get_state().e_stop)
|
|
mc_reset();
|
|
drop = true;
|
|
break;
|
|
|
|
#if COMPATIBILITY_LEVEL == 0
|
|
case CMD_EXIT: // Call motion control reset routine.
|
|
mc_reset();
|
|
sys.flags.exit = On;
|
|
drop = true;
|
|
break;
|
|
#endif
|
|
|
|
case CMD_STATUS_REPORT_ALL: // Add all statuses to report
|
|
system_add_rt_report(report_get_rt_flags_all().value);
|
|
system_set_exec_state_flag(EXEC_STATUS_REPORT);
|
|
drop = true;
|
|
break;
|
|
|
|
case CMD_STATUS_REPORT:
|
|
case 0x05:
|
|
if(!sys.flags.auto_reporting)
|
|
system_set_exec_state_flag(EXEC_STATUS_REPORT);
|
|
drop = true;
|
|
break;
|
|
|
|
case CMD_CYCLE_START:
|
|
signals.cycle_start = On;
|
|
break;
|
|
|
|
case CMD_FEED_HOLD:
|
|
system_set_exec_state_flag(EXEC_FEED_HOLD);
|
|
drop = true;
|
|
break;
|
|
|
|
case CMD_SAFETY_DOOR:
|
|
if((drop = state_get() != STATE_SAFETY_DOOR)) {
|
|
sys.flags.is_parking = settings.parking.flags.enabled;
|
|
system_set_exec_state_flag(EXEC_SAFETY_DOOR);
|
|
}
|
|
break;
|
|
|
|
case CMD_JOG_CANCEL:
|
|
char_counter = 0;
|
|
drop = true;
|
|
hal.stream.cancel_read_buffer();
|
|
#ifdef KINEMATICS_API // needed when kinematics algorithm segments long jog distances (as it blocks reading from input stream)
|
|
if (state_get() & STATE_JOG) // Block all other states from invoking motion cancel.
|
|
system_set_exec_state_flag(EXEC_MOTION_CANCEL);
|
|
#endif
|
|
if(grbl.on_jog_cancel)
|
|
grbl.on_jog_cancel(state_get());
|
|
break;
|
|
|
|
case CMD_GCODE_REPORT:
|
|
system_set_exec_state_flag(EXEC_GCODE_REPORT);
|
|
drop = true;
|
|
break;
|
|
|
|
case CMD_PROBE_CONNECTED_TOGGLE:
|
|
if((drop = !!hal.probe.connected_toggle))
|
|
task_add_immediate(probe_connected_event, (void *)2);
|
|
break;
|
|
|
|
case CMD_OPTIONAL_STOP_TOGGLE:
|
|
if((drop = (signals.stop_disable = !hal.signals_cap.stop_disable))) // Not available as realtime command if HAL supports physical switch
|
|
sys.flags.optional_stop_disable = !sys.flags.optional_stop_disable;
|
|
break;
|
|
|
|
case CMD_SINGLE_BLOCK_TOGGLE:
|
|
if((drop = (signals.single_block = !hal.signals_cap.single_block))) // Not available as realtime command if HAL supports physical switch
|
|
sys.flags.single_block = !sys.flags.single_block;
|
|
break;
|
|
|
|
case CMD_PID_REPORT:
|
|
system_set_exec_state_flag(EXEC_PID_REPORT);
|
|
drop = true;
|
|
break;
|
|
|
|
case CMD_MPG_MODE_TOGGLE: // Switch off MPG mode
|
|
if((drop = hal.stream.type == StreamType_MPG))
|
|
task_add_immediate(stream_mpg_set_mode, NULL);
|
|
break;
|
|
|
|
case CMD_AUTO_REPORTING_TOGGLE:
|
|
if((drop = settings.report_interval != 0))
|
|
sys.flags.auto_reporting = !sys.flags.auto_reporting;
|
|
break;
|
|
|
|
case CMD_OVERRIDE_FEED_RESET:
|
|
case CMD_OVERRIDE_FEED_COARSE_PLUS:
|
|
case CMD_OVERRIDE_FEED_COARSE_MINUS:
|
|
case CMD_OVERRIDE_FEED_FINE_PLUS:
|
|
case CMD_OVERRIDE_FEED_FINE_MINUS:
|
|
case CMD_OVERRIDE_RAPID_RESET:
|
|
case CMD_OVERRIDE_RAPID_MEDIUM:
|
|
case CMD_OVERRIDE_RAPID_LOW:
|
|
drop = true;
|
|
enqueue_feed_override(c);
|
|
break;
|
|
|
|
case CMD_OVERRIDE_SPINDLE_RESET:
|
|
case CMD_OVERRIDE_SPINDLE_COARSE_PLUS:
|
|
case CMD_OVERRIDE_SPINDLE_COARSE_MINUS:
|
|
case CMD_OVERRIDE_SPINDLE_FINE_PLUS:
|
|
case CMD_OVERRIDE_SPINDLE_FINE_MINUS:
|
|
case CMD_OVERRIDE_SPINDLE_STOP:
|
|
drop = true;
|
|
enqueue_spindle_override((uint8_t)c);
|
|
break;
|
|
|
|
case CMD_OVERRIDE_COOLANT_FLOOD_TOGGLE:
|
|
case CMD_OVERRIDE_COOLANT_MIST_TOGGLE:
|
|
case CMD_OVERRIDE_FAN0_TOGGLE:
|
|
drop = true;
|
|
enqueue_coolant_override((uint8_t)c);
|
|
break;
|
|
|
|
case CMD_REBOOT:
|
|
if(esc && hal.reboot)
|
|
hal.reboot(); // Force MCU reboot. This call should never return.
|
|
break;
|
|
|
|
default:
|
|
if(((unsigned char)c < ' ' && c != ASCII_BS) || ((unsigned char)c > ASCII_DEL && (unsigned char)c <= 0xBF))
|
|
drop = grbl.on_unknown_realtime_cmd == NULL || grbl.on_unknown_realtime_cmd(c);
|
|
break;
|
|
}
|
|
|
|
// 2. Process printable ASCII characters and top-bit set characters
|
|
// If legacy realtime commands are disabled they are returned to the input stream
|
|
// when appearing in settings ($ commands) or comments
|
|
|
|
if(!drop) switch ((unsigned char)c) {
|
|
|
|
case CMD_STATUS_REPORT_LEGACY:
|
|
if(!keep_rt_commands || settings.flags.legacy_rt_commands) {
|
|
system_set_exec_state_flag(EXEC_STATUS_REPORT);
|
|
drop = true;
|
|
}
|
|
break;
|
|
|
|
case CMD_CYCLE_START_LEGACY:
|
|
signals.cycle_start = !keep_rt_commands || settings.flags.legacy_rt_commands;
|
|
break;
|
|
|
|
case CMD_FEED_HOLD_LEGACY:
|
|
if(!keep_rt_commands || settings.flags.legacy_rt_commands) {
|
|
system_set_exec_state_flag(EXEC_FEED_HOLD);
|
|
drop = true;
|
|
}
|
|
break;
|
|
|
|
default: // Drop top bit set characters
|
|
drop = !(keep_rt_commands || (unsigned char)c < 0x7F);
|
|
break;
|
|
}
|
|
|
|
esc = c == ASCII_ESC;
|
|
|
|
if(signals.bits) {
|
|
|
|
if(grbl.on_control_signals_changed)
|
|
grbl.on_control_signals_changed(signals);
|
|
|
|
if(signals.cycle_start) {
|
|
system_set_exec_state_flag(EXEC_CYCLE_START);
|
|
// Cancel any pending tool change
|
|
gc_state.tool_change = false;
|
|
drop = true;
|
|
if(grbl.on_cycle_start)
|
|
grbl.on_cycle_start();
|
|
}
|
|
}
|
|
|
|
return drop;
|
|
}
|
|
|
|
void protocol_execute_noop (sys_state_t state)
|
|
{
|
|
(void)state;
|
|
}
|