Overhauled state machine. New safety door feature.
- Overhauled the state machine and cleaned up its overall operation. This involved creating a new ‘suspend’ state for what all external commands, except real-time commands, are ignored. All hold type states enter this suspend state. - Removed ‘auto cycle start’ setting from Grbl. This was not used by users in its intended way and is somewhat redundant, as GUI manage the cycle start by streaming. It also muddled up how Grbl should interpret how and when to execute a g-code block. Removing it made everything much much simpler. - Fixed a program pause bug when used with other buffer_sync commands. - New safety door feature for OEMs. Immediately forces a feed hold and then de-energizes the machine. Resuming is blocked until the door is closed. When it is, it re-energizes the system and then resumes on the normal toolpath. - Safety door input pin is optional and uses the feed hold pin on A1. Enabled by config.h define. - Spindle and coolant re-energizing upon a safety door resume has a programmable delay time to allow for complete spin up to rpm and turning on the coolant before resuming motion. - Safety door-style feed holds can be used instead of regular feed hold (doesn’t de-energize the machine) with a ‘@‘ character. If the safety door input pin is not enabled, the system can be resumed at any time.
This commit is contained in:
163
grbl/protocol.c
163
grbl/protocol.c
@ -73,8 +73,13 @@ void protocol_main_loop()
|
||||
if (sys.state == STATE_ALARM) {
|
||||
report_feedback_message(MESSAGE_ALARM_LOCK);
|
||||
} else {
|
||||
// All systems go!
|
||||
sys.state = STATE_IDLE; // Set system to ready. Clear all state flags.
|
||||
// All systems go! But first check for safety door.
|
||||
if (system_check_safety_door_ajar()) {
|
||||
bit_true(sys.rt_exec_state, EXEC_SAFETY_DOOR);
|
||||
protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state.
|
||||
} else {
|
||||
sys.state = STATE_IDLE; // Set system to ready. Clear all state flags.
|
||||
}
|
||||
system_execute_startup(line); // Execute startup script.
|
||||
}
|
||||
|
||||
@ -175,7 +180,9 @@ void protocol_main_loop()
|
||||
void protocol_execute_realtime()
|
||||
{
|
||||
uint8_t rt_exec; // Temp variable to avoid calling volatile multiple times.
|
||||
|
||||
|
||||
do { // If system is suspended, suspend loop restarts here.
|
||||
|
||||
// Check and execute alarms.
|
||||
rt_exec = sys.rt_exec_alarm; // Copy volatile sys.rt_exec_alarm.
|
||||
if (rt_exec) { // Enter only if any bit flag is true
|
||||
@ -210,6 +217,7 @@ void protocol_execute_realtime()
|
||||
// Check amd execute realtime commands
|
||||
rt_exec = sys.rt_exec_state; // Copy volatile sys.rt_exec_state.
|
||||
if (rt_exec) { // Enter only if any bit flag is true
|
||||
|
||||
// Execute system abort.
|
||||
if (rt_exec & EXEC_RESET) {
|
||||
sys.abort = true; // Only place this is set true.
|
||||
@ -221,30 +229,88 @@ void protocol_execute_realtime()
|
||||
report_realtime_status();
|
||||
bit_false_atomic(sys.rt_exec_state,EXEC_STATUS_REPORT);
|
||||
}
|
||||
|
||||
// Execute a feed hold with deceleration, only during cycle.
|
||||
if (rt_exec & EXEC_FEED_HOLD) {
|
||||
// !!! During a cycle, the segment buffer has just been reloaded and full. So the math involved
|
||||
// with the feed hold should be fine for most, if not all, operational scenarios.
|
||||
if (sys.state == STATE_CYCLE) {
|
||||
sys.state = STATE_HOLD;
|
||||
st_update_plan_block_parameters();
|
||||
st_prep_buffer();
|
||||
sys.auto_start = false; // Disable planner auto start upon feed hold.
|
||||
}
|
||||
bit_false_atomic(sys.rt_exec_state,EXEC_FEED_HOLD);
|
||||
}
|
||||
|
||||
// Execute hold states.
|
||||
// NOTE: The math involved to calculate the hold should be low enough for most, if not all,
|
||||
// operational scenarios. Once hold is initiated, the system enters a suspend state to block
|
||||
// all main program processes until either reset or resumed.
|
||||
if (rt_exec & (EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR)) {
|
||||
|
||||
// TODO: CHECK MODE? How to handle this? Likely nothing, since it only works when IDLE and then resets Grbl.
|
||||
|
||||
// State check for allowable states for hold methods.
|
||||
if ((sys.state == STATE_IDLE) || (sys.state & (STATE_CYCLE | STATE_HOMING | STATE_MOTION_CANCEL | STATE_HOLD | STATE_SAFETY_DOOR))) {
|
||||
|
||||
// If in CYCLE state, all hold states immediately initiate a motion HOLD.
|
||||
if (sys.state == STATE_CYCLE) {
|
||||
st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration.
|
||||
sys.suspend = SUSPEND_ENABLE_HOLD; // Initiate holding cycle with flag.
|
||||
}
|
||||
// If IDLE, Grbl is not in motion. Simply indicate suspend ready state.
|
||||
if (sys.state == STATE_IDLE) { sys.suspend = SUSPEND_ENABLE_READY; }
|
||||
|
||||
// Execute a cycle start by starting the stepper interrupt begin executing the blocks in queue.
|
||||
if (rt_exec & EXEC_CYCLE_START) {
|
||||
if (sys.state == STATE_QUEUED) {
|
||||
sys.state = STATE_CYCLE;
|
||||
st_prep_buffer(); // Initialize step segment buffer before beginning cycle.
|
||||
st_wake_up();
|
||||
if (bit_istrue(settings.flags,BITFLAG_AUTO_START)) {
|
||||
sys.auto_start = true; // Re-enable auto start after feed hold.
|
||||
} else {
|
||||
sys.auto_start = false; // Reset auto start per settings.
|
||||
// Execute and flag a motion cancel with deceleration and return to idle. Used primarily by probing cycle
|
||||
// to halt and cancel the remainder of the motion.
|
||||
if (rt_exec & EXEC_MOTION_CANCEL) {
|
||||
// MOTION_CANCEL only occurs during a CYCLE, but a HOLD and SAFETY_DOOR may been initiated beforehand
|
||||
// to hold the CYCLE. If so, only flag that motion cancel is complete.
|
||||
if (sys.state == STATE_CYCLE) { sys.state = STATE_MOTION_CANCEL; }
|
||||
sys.suspend |= SUSPEND_MOTION_CANCEL; // Indicate motion cancel when resuming. Special motion complete.
|
||||
}
|
||||
|
||||
// Execute a feed hold with deceleration, only during cycle.
|
||||
if (rt_exec & EXEC_FEED_HOLD) {
|
||||
// Block SAFETY_DOOR state from prematurely changing back to HOLD.
|
||||
if (bit_isfalse(sys.state,STATE_SAFETY_DOOR)) { sys.state = STATE_HOLD; }
|
||||
}
|
||||
|
||||
// Execute a safety door stop with a feed hold, only during a cycle, and disable spindle/coolant.
|
||||
// NOTE: Safety door differs from feed holds by stopping everything no matter state, disables powered
|
||||
// devices (spindle/coolant), and blocks resuming until switch is re-engaged. The power-down is
|
||||
// executed here, if IDLE, or when the CYCLE completes via the EXEC_CYCLE_STOP flag.
|
||||
if (rt_exec & EXEC_SAFETY_DOOR) {
|
||||
report_feedback_message(MESSAGE_SAFETY_DOOR_AJAR);
|
||||
// If already in active, ready-to-resume HOLD, set CYCLE_STOP flag to force de-energize.
|
||||
// NOTE: Only temporarily sets the 'rt_exec' variable, not the volatile 'rt_exec_state' variable.
|
||||
if (sys.suspend & SUSPEND_ENABLE_READY) { bit_true(rt_exec,EXEC_CYCLE_STOP); }
|
||||
sys.suspend |= SUSPEND_ENERGIZE;
|
||||
sys.state = STATE_SAFETY_DOOR;
|
||||
}
|
||||
|
||||
}
|
||||
bit_false_atomic(sys.rt_exec_state,(EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR));
|
||||
}
|
||||
|
||||
// Execute a cycle start by starting the stepper interrupt to begin executing the blocks in queue.
|
||||
if (rt_exec & EXEC_CYCLE_START) {
|
||||
// Block if called at same time as the hold commands: feed hold, motion cancel, and safety door.
|
||||
// Ensures auto-cycle-start doesn't resume a hold without an explicit user-input.
|
||||
if (!(rt_exec & (EXEC_FEED_HOLD | EXEC_MOTION_CANCEL | EXEC_SAFETY_DOOR))) {
|
||||
// Cycle start only when IDLE or when a hold is complete and ready to resume.
|
||||
// NOTE: SAFETY_DOOR is implicitly blocked. It reverts to HOLD when the door is closed.
|
||||
if ((sys.state == STATE_IDLE) || ((sys.state & (STATE_HOLD | STATE_MOTION_CANCEL)) && (sys.suspend & SUSPEND_ENABLE_READY))) {
|
||||
// Re-energize powered components, if disabled by SAFETY_DOOR.
|
||||
if (sys.suspend & SUSPEND_ENERGIZE) {
|
||||
// Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle.
|
||||
if (gc_state.modal.spindle != SPINDLE_DISABLE) {
|
||||
spindle_set_state(gc_state.modal.spindle, gc_state.spindle_speed);
|
||||
delay_ms(SAFETY_DOOR_SPINDLE_DELAY); // TODO: Blocking function call. Need a non-blocking one eventually.
|
||||
}
|
||||
if (gc_state.modal.coolant != COOLANT_DISABLE) {
|
||||
coolant_set_state(gc_state.modal.coolant);
|
||||
delay_ms(SAFETY_DOOR_COOLANT_DELAY); // TODO: Blocking function call. Need a non-blocking one eventually.
|
||||
}
|
||||
// TODO: Install return to pre-park position.
|
||||
}
|
||||
// Start cycle only if queued motions exist in planner buffer and the motion is not canceled.
|
||||
if (plan_get_current_block() && bit_isfalse(sys.suspend,SUSPEND_MOTION_CANCEL)) {
|
||||
sys.state = STATE_CYCLE;
|
||||
st_prep_buffer(); // Initialize step segment buffer before beginning cycle.
|
||||
st_wake_up();
|
||||
} else { // Otherwise, do nothing. Set and resume IDLE state.
|
||||
sys.state = STATE_IDLE;
|
||||
}
|
||||
sys.suspend = SUSPEND_DISABLE; // Break suspend state.
|
||||
}
|
||||
}
|
||||
bit_false_atomic(sys.rt_exec_state,EXEC_CYCLE_START);
|
||||
@ -256,17 +322,41 @@ void protocol_execute_realtime()
|
||||
// cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
|
||||
// NOTE: EXEC_CYCLE_STOP is set by the stepper subsystem when a cycle or feed hold completes.
|
||||
if (rt_exec & EXEC_CYCLE_STOP) {
|
||||
if ( plan_get_current_block() ) { sys.state = STATE_QUEUED; }
|
||||
else { sys.state = STATE_IDLE; }
|
||||
if (sys.state & (STATE_HOLD | STATE_SAFETY_DOOR)) {
|
||||
// Hold complete. Set to indicate ready to resume. Remain in HOLD or DOOR states until user
|
||||
// has issued a resume command or reset.
|
||||
if (sys.suspend & SUSPEND_ENERGIZE) { // De-energize system if safety door has been opened.
|
||||
spindle_stop();
|
||||
coolant_stop();
|
||||
// TODO: Install parking motion here.
|
||||
}
|
||||
bit_true(sys.suspend,SUSPEND_ENABLE_READY);
|
||||
} else { // Motion is complete. Includes CYCLE, HOMING, and MOTION_CANCEL states.
|
||||
sys.suspend = SUSPEND_DISABLE;
|
||||
sys.state = STATE_IDLE;
|
||||
}
|
||||
bit_false_atomic(sys.rt_exec_state,EXEC_CYCLE_STOP);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Overrides flag byte (sys.override) and execution should be installed here, since they
|
||||
// are realtime and require a direct and controlled interface to the main stepper program.
|
||||
|
||||
// Reload step segment buffer
|
||||
if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_HOMING)) { st_prep_buffer(); }
|
||||
if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_MOTION_CANCEL | STATE_SAFETY_DOOR | STATE_HOMING)) { st_prep_buffer(); }
|
||||
|
||||
// If safety door was opened, actively check when safety door is closed and ready to resume.
|
||||
// NOTE: This unlocks the SAFETY_DOOR state to a HOLD state, such that CYCLE_START can activate a resume.
|
||||
if (sys.state == STATE_SAFETY_DOOR) {
|
||||
if (bit_istrue(sys.suspend,SUSPEND_ENABLE_READY)) {
|
||||
if (!(system_check_safety_door_ajar())) {
|
||||
sys.state = STATE_HOLD; // Update to HOLD state to indicate door is closed and ready to resume.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} while(sys.suspend); // Check for system suspend state before exiting.
|
||||
|
||||
}
|
||||
|
||||
@ -277,12 +367,10 @@ void protocol_buffer_synchronize()
|
||||
{
|
||||
// If system is queued, ensure cycle resumes if the auto start flag is present.
|
||||
protocol_auto_cycle_start();
|
||||
// Check and set auto start to resume cycle after synchronize and caller completes.
|
||||
if (sys.state == STATE_CYCLE) { sys.auto_start = true; }
|
||||
while (plan_get_current_block() || (sys.state == STATE_CYCLE)) {
|
||||
do {
|
||||
protocol_execute_realtime(); // Check and execute run-time commands
|
||||
if (sys.abort) { return; } // Check for system abort
|
||||
}
|
||||
} while (plan_get_current_block() || (sys.state == STATE_CYCLE));
|
||||
}
|
||||
|
||||
|
||||
@ -293,7 +381,8 @@ void protocol_buffer_synchronize()
|
||||
// as a beginner tool, but (1.) still operates. If disabled, the operation of cycle start is
|
||||
// manually issuing a cycle start command whenever the user is ready and there is a valid motion
|
||||
// command in the planner queue.
|
||||
// NOTE: This function is called from the main loop and mc_line() only and executes when one of
|
||||
// two conditions exist respectively: There are no more blocks sent (i.e. streaming is finished,
|
||||
// single commands), or the planner buffer is full and ready to go.
|
||||
void protocol_auto_cycle_start() { if (sys.auto_start) { bit_true_atomic(sys.rt_exec_state, EXEC_CYCLE_START); } }
|
||||
// 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() { bit_true_atomic(sys.rt_exec_state, EXEC_CYCLE_START); }
|
||||
|
Reference in New Issue
Block a user