1
0
mirror of https://github.com/bdring/Grbl_Esp32.git synced 2025-09-01 02:21:46 +02:00

Changed control pins based on the ISR_WIP branch

Added user defined pins
This commit is contained in:
Stefan de Bruijn
2021-05-25 12:28:48 +02:00
parent 6eac36c66f
commit 6bd74b3585
22 changed files with 819 additions and 592 deletions

View File

@@ -167,15 +167,15 @@ bool user_defined_homing(AxisMask cycle_mask) {
}
st_prep_buffer(); // Check and prep segment buffer. NOTE: Should take no longer than 200us.
// Exit routines: No time to run protocol_execute_realtime() in this loop.
if (sys_rt_exec_state.bit.safetyDoor || sys_rt_exec_state.bit.reset || cycle_stop) {
if (rtSafetyDoor || rtReset || cycle_stop) {
ExecState rt_exec_state;
rt_exec_state.value = sys_rt_exec_state.value;
// Homing failure condition: Reset issued during cycle.
if (rt_exec_state.bit.reset) {
if (rtReset) {
sys_rt_exec_alarm = ExecAlarm::HomingFailReset;
}
// Homing failure condition: Safety door was opened.
if (rt_exec_state.bit.safetyDoor) {
if (rtSafetyDoor) {
sys_rt_exec_alarm = ExecAlarm::HomingFailDoor;
}
// Homing failure condition: Limit switch still engaged after pull-off motion

View File

@@ -23,22 +23,12 @@
#include "Grbl.h"
#include "Control.h"
// TODO: the plumbing to communicate with System.cpp is not yet done.
// These variables are placeholders.
bool rtSafetyDoor;
bool rtReset;
bool rtFeedHold;
bool rtCycleStart;
bool rtButtonMacro0;
bool rtButtonMacro1;
bool rtButtonMacro2;
bool rtButtonMacro3;
#include "System.h"
Control::Control() :
_safetyDoor(0, &rtSafetyDoor, "Door", 'D'), _reset(1, &rtReset, "Reset", 'R'), _feedHold(2, &rtFeedHold, "FeedHold", 'H'),
_cycleStart(3, &rtCycleStart, "CycleStart", 'S'), _macro0(4, &rtButtonMacro0, "Macro 0", '0'),
_macro1(5, &rtButtonMacro1, "Macro 1", '1'), _macro2(6, &rtButtonMacro2, "Macro 2", '2'), _macro3(7, &rtButtonMacro3, "Macro 3", '3') {}
_safetyDoor(0, rtSafetyDoor, "Door", 'D'), _reset(1, rtReset, "Reset", 'R'), _feedHold(2, rtFeedHold, "FeedHold", 'H'),
_cycleStart(3, rtCycleStart, "CycleStart", 'S'), _macro0(4, rtButtonMacro0, "Macro 0", '0'), _macro1(5, rtButtonMacro1, "Macro 1", '1'),
_macro2(6, rtButtonMacro2, "Macro 2", '2'), _macro3(7, rtButtonMacro3, "Macro 3", '3') {}
void Control::init() {
_safetyDoor.init();
@@ -63,12 +53,22 @@ void Control::handle(Configuration::HandlerBase& handler) {
handler.handle("macro2", _macro2._pin);
handler.handle("macro3", _macro3._pin);
}
#if 0
void Control::report(char* status, bool& pinReportStarted) {
_safetyDoor.report(status, pinReportStarted);
_reset.report(status, pinReportStarted);
_feedHold.report(status, pinReportStarted);
_cycleStart.report(status, pinReportStarted);
_macro0.report(status, pinReportStarted);
_macro1.report(status, pinReportStarted);
_macro2.report(status, pinReportStarted);
_macro3.report(status, pinReportStarted);
}
// Returns if safety door is ajar(T) or closed(F), based on pin state.
bool system_check_safety_door_ajar() {
bool Control::system_check_safety_door_ajar() {
// If a safety door pin is not defined, this will return false
// because that is the default for the value field, which will
// never be changed for an undefined pin.
return _safetyDoor.get();
}
#endif

View File

@@ -30,6 +30,8 @@
class Control : public Configuration::Configurable {
private:
// TODO: Should we not just put this in an array so we can enumerate it easily?
ControlPin _safetyDoor;
ControlPin _reset;
ControlPin _feedHold;
@@ -49,5 +51,8 @@ public:
void validate() const override;
void handle(Configuration::HandlerBase& handler) override;
bool system_check_safety_door_ajar();
void report(char* status, bool& pinReportStarted);
~Control() = default;
};

View File

@@ -8,10 +8,7 @@
void IRAM_ATTR ControlPin::handleISR() {
bool pinState = _pin.read();
_value = pinState;
if (_rtVariable) {
*_rtVariable = pinState;
}
_rtVariable = pinState;
}
void ControlPin::init() {
@@ -25,3 +22,19 @@ void ControlPin::init() {
_pin.attachInterrupt<ControlPin, &ControlPin::handleISR>(this, CHANGE);
}
}
void ControlPin::report(char* status, bool& pinReportStarted) {
if (!_pin.undefined()) {
if (!pinReportStarted) {
strcat(status, "|Pn:");
pinReportStarted = true;
}
size_t pos = strlen(status);
status[pos] = letter();
status[pos + 1] = '\0';
}
}
ControlPin::~ControlPin() {
_pin.detachInterrupt();
}

View File

@@ -9,20 +9,26 @@ private:
bool _invertBitNum;
bool _value;
const char _letter;
volatile bool* _rtVariable;
volatile bool& _rtVariable;
const char* _legend;
void IRAM_ATTR handleISR();
public:
ControlPin(uint8_t bitNum, volatile bool* rtVariable, const char* legend, char letter) :
_invertBitNum(bitNum), _value(false), _letter(letter), _rtVariable(rtVariable), _legend(legend) {}
ControlPin(uint8_t bitNum, volatile bool& rtVariable, const char* legend, char letter) :
_invertBitNum(bitNum), _value(false), _letter(letter), _rtVariable(rtVariable), _legend(legend) {
_rtVariable = _value;
}
Pin _pin;
void init();
bool get() { return _value; }
const char* legend() { return _legend; }
// char invertBitNum() { return _invertBitNum; }
char letter() { return _letter; }
void init();
void report(char* status, bool& pinReportStarted);
~ControlPin();
};

View File

@@ -2,29 +2,6 @@
#include <map>
// System executor bit map. Used internally by realtime protocol as realtime command flags,
// which notifies the main program to execute the specified realtime command asynchronously.
// NOTE: The system executor uses an unsigned 8-bit volatile variable (8 flag limit.) The default
// flags are always false, so the realtime protocol only needs to check for a non-zero value to
// know when there is a realtime command to execute.
struct ExecStateBits {
uint8_t statusReport : 1;
uint8_t cycleStart : 1;
uint8_t cycleStop : 1; // Unused, per cycle_stop variable
uint8_t feedHold : 1;
uint8_t reset : 1;
uint8_t safetyDoor : 1;
uint8_t motionCancel : 1;
uint8_t sleep : 1;
};
union ExecState {
uint8_t value;
ExecStateBits bit;
};
static_assert(sizeof(ExecStateBits) == sizeof(uint8_t), "ExecStateBits is not an uint8");
// Override bit maps. Realtime bitflags to control feed, rapid, spindle, and coolant overrides.
// Spindle/coolant and feed/rapids are separated into two controlling flag variables.

View File

@@ -1561,8 +1561,8 @@ Error gc_execute_line(char* line, uint8_t client) {
case ProgramFlow::Paused:
protocol_buffer_synchronize(); // Sync and finish all remaining buffered motions before moving on.
if (sys.state != State::CheckMode) {
sys_rt_exec_state.bit.feedHold = true; // Use feed hold for program pause.
protocol_execute_realtime(); // Execute suspend.
rtFeedHold = true; // Use feed hold for program pause.
protocol_execute_realtime(); // Execute suspend.
}
break;
case ProgramFlow::CompletedM2:

View File

@@ -66,7 +66,8 @@ void grbl_init() {
config->_axes->init();
grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Initializing system...");
system_ini(); // Configure pinout pins and pin-change interrupt (Renamed due to conflict with esp32 files)
config->_control->init();
init_output_pins(); // Configure pinout pins and pin-change interrupt (Renamed due to conflict with esp32 files)
memset(sys_position, 0, sizeof(sys_position)); // Clear machine position.
machine_init(); // user supplied function for special initialization
@@ -126,10 +127,16 @@ static void reset_variables() {
memset(sys_probe_position, 0, sizeof(sys_probe_position)); // Clear probe position.
sys_probe_state = ProbeState::Off;
sys_rt_exec_state.value = 0;
rtStatusReport = false;
rtCycleStart = false;
rtFeedHold = false;
rtReset = false;
rtSafetyDoor = false;
rtMotionCancel = false;
rtSleep = false;
rtCycleStop = false;
sys_rt_exec_accessory_override.value = 0;
sys_rt_exec_alarm = ExecAlarm::None;
cycle_stop = false;
sys_rt_f_override = FeedOverride::Default;
sys_rt_r_override = RapidOverride::Default;
sys_rt_s_override = SpindleSpeedOverride::Default;

View File

@@ -63,6 +63,7 @@ const char* const GRBL_VERSION_BUILD = "20210326";
#include "Settings.h"
#include "SettingsDefinitions.h"
#include "WebUI/WebSettings.h"
#include "ControlPin.h"
#include "UserOutput.h"

View File

@@ -221,15 +221,13 @@ void limits_go_home(uint8_t cycle_mask, uint n_locate_cycles) {
}
st_prep_buffer(); // Check and prep segment buffer. NOTE: Should take no longer than 200us.
// Exit routines: No time to run protocol_execute_realtime() in this loop.
if (sys_rt_exec_state.bit.safetyDoor || sys_rt_exec_state.bit.reset || cycle_stop) {
ExecState rt_exec_state;
rt_exec_state.value = sys_rt_exec_state.value;
if (rtSafetyDoor || rtReset || rtCycleStop) {
// Homing failure condition: Reset issued during cycle.
if (rt_exec_state.bit.reset) {
if (rtReset) {
sys_rt_exec_alarm = ExecAlarm::HomingFailReset;
}
// Homing failure condition: Safety door was opened.
if (rt_exec_state.bit.safetyDoor) {
if (rtSafetyDoor) {
sys_rt_exec_alarm = ExecAlarm::HomingFailDoor;
}
// Homing failure condition: Limit switch still engaged after pull-off motion
@@ -237,7 +235,7 @@ void limits_go_home(uint8_t cycle_mask, uint n_locate_cycles) {
sys_rt_exec_alarm = ExecAlarm::HomingFailPulloff;
}
// Homing failure condition: Limit switch not found during approach.
if (approach && cycle_stop) {
if (approach && rtCycleStop) {
sys_rt_exec_alarm = ExecAlarm::HomingFailApproach;
}
@@ -249,7 +247,7 @@ void limits_go_home(uint8_t cycle_mask, uint n_locate_cycles) {
return;
} else {
// Pull-off motion complete. Disable CYCLE_STOP from executing.
cycle_stop = false;
rtCycleStop = false;
break;
}
}
@@ -386,7 +384,7 @@ void limits_soft_check(float* target) {
// workspace volume so just come to a controlled stop so position is not lost. When complete
// enter alarm mode.
if (sys.state == State::Cycle) {
sys_rt_exec_state.bit.feedHold = true;
rtFeedHold = true;
do {
protocol_execute_realtime();
if (sys.abort) {

View File

@@ -344,6 +344,19 @@ void SPIBus::handle(Configuration::HandlerBase& handler) {
handler.handle("mosi", _sck);
}
void UserOutputs::validate() const {}
void UserOutputs::handle(Configuration::HandlerBase& handler) {
handler.handle("analog0", _analogOutput[0]);
handler.handle("analog1", _analogOutput[1]);
handler.handle("analog2", _analogOutput[2]);
handler.handle("analog3", _analogOutput[3]);
handler.handle("digital0", _digitalOutput[0]);
handler.handle("digital1", _digitalOutput[1]);
handler.handle("digital2", _digitalOutput[2]);
handler.handle("digital3", _digitalOutput[3]);
}
void MachineConfig::validate() const {}
void MachineConfig::handle(Configuration::HandlerBase& handler) {
@@ -357,7 +370,9 @@ void MachineConfig::handle(Configuration::HandlerBase& handler) {
handler.handle("board", _board);
handler.handle("name", _name);
handler.handle("idle_time", _idleTime);
// TODO: Consider putting these under a gcode: hierarchy level
handler.handle("user_outputs", _userOutputs);
// TODO: Consider putting these under a gcode: hierarchy level? Or motion control?
handler.handle("laser_mode", _laserMode);
handler.handle("arc_tolerance", _arcTolerance);
handler.handle("junction_deviation", _junctionDeviation);
@@ -380,6 +395,15 @@ void MachineConfig::afterParse() {
log_info("Probe config missing; building default probe");
_probe = new Probe();
}
if (_userOutputs == nullptr) {
_userOutputs = new UserOutputs();
}
if (_control == nullptr) {
log_info("Control config missing; building default");
_control = new Control();
}
}
size_t MachineConfig::readFile(const char* filename, char*& buffer) {

View File

@@ -202,6 +202,19 @@ public:
~SPIBus() = default;
};
class UserOutputs : public Configuration::Configurable {
public:
UserOutputs() = default;
Pin _analogOutput[4];
Pin _digitalOutput[4];
void validate() const override;
void handle(Configuration::HandlerBase& handler) override;
~UserOutputs() = default;
};
class WifiConfig : public Configuration::Configurable {
public:
WifiConfig() = default;
@@ -351,13 +364,14 @@ class MachineConfig : public Configuration::Configurable {
public:
MachineConfig() = default;
Axes* _axes = nullptr;
SPIBus* _spi = nullptr;
I2SOBus* _i2so = nullptr;
CoolantControl* _coolant = nullptr;
Probe* _probe = nullptr;
Communications* _comms = nullptr;
Control* _control = nullptr;
Axes* _axes = nullptr;
SPIBus* _spi = nullptr;
I2SOBus* _i2so = nullptr;
CoolantControl* _coolant = nullptr;
Probe* _probe = nullptr;
Communications* _comms = nullptr;
Control* _control = nullptr;
UserOutputs* _userOutputs = nullptr;
int _pulseMicroSeconds = 3;
int _directionDelayMilliSeconds = 0;

View File

@@ -450,7 +450,7 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, uint8_t par
// Activate the probing state monitor in the stepper module.
sys_probe_state = ProbeState::Active;
// Perform probing cycle. Wait here until probe is triggered or motion completes.
sys_rt_exec_state.bit.cycleStart = true;
rtCycleStart = true;
do {
protocol_execute_realtime();
if (sys.abort) {
@@ -534,8 +534,8 @@ void mc_override_ctrl_update(Override override_state) {
void mc_reset() {
grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Debug, "mc_reset()");
// Only this function can set the system reset. Helps prevent multiple kill calls.
if (!sys_rt_exec_state.bit.reset) {
sys_rt_exec_state.bit.reset = true;
if (!rtReset) {
rtReset = true;
// Kill spindle and coolant.
spindle->stop();

View File

@@ -28,6 +28,8 @@
#include <cstdint>
#include <cstring>
// TODO: ENABLE_CONTROL_SW_DEBOUNCE should end up here with a shared task.
// #define PIN_DEBUG // Pin debugging. WILL spam you with a lot of data!
// Forward declarations:

View File

@@ -64,7 +64,7 @@ void Probe::state_monitor() {
if (get_state() ^ _isProbeAway) {
sys_probe_state = ProbeState::Off;
memcpy(sys_probe_position, sys_position, sizeof(sys_position));
sys_rt_exec_state.bit.motionCancel = true;
rtMotionCancel = true;
}
}

View File

@@ -2,6 +2,8 @@
#include <map>
#include "Regex.h"
#include "MachineConfig.h"
// WG Readable and writable as guest
// WU Readable and writable as user and admin
// WA Readable as user and admin, writable as admin
@@ -222,7 +224,7 @@ Error toggle_check_mode(const char* value, WebUI::AuthenticationLevel auth_level
Error disable_alarm_lock(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {
if (sys.state == State::Alarm) {
// Block if safety door is ajar.
if (system_check_safety_door_ajar()) {
if (config->_control->system_check_safety_door_ajar()) {
return Error::CheckDoor;
}
report_feedback_message(Message::AlarmUnlock);
@@ -239,7 +241,7 @@ Error home(int cycle) {
if (homingAxes()) {
return Error::SettingDisabled;
}
if (system_check_safety_door_ajar()) {
if (config->_control->system_check_safety_door_ajar()) {
return Error::CheckDoor; // Block if safety door is ajar.
}
sys.state = State::Homing; // Set system state variable
@@ -291,7 +293,7 @@ Error home_c(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ES
return home(bit(C_AXIS));
}
Error sleep_grbl(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {
sys_rt_exec_state.bit.sleep = true;
rtSleep = true;
return Error::Ok;
}
Error get_report_build_info(const char* value, WebUI::AuthenticationLevel auth_level, WebUI::ESPResponseStream* out) {

View File

@@ -126,8 +126,8 @@ void protocol_main_loop() {
} else {
// Check if the safety door is open.
sys.state = State::Idle;
if (system_check_safety_door_ajar()) {
sys_rt_exec_state.bit.safetyDoor = true;
if (config->_control->system_check_safety_door_ajar()) {
rtSafetyDoor = true;
protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state.
}
// All systems go!
@@ -224,8 +224,8 @@ void protocol_buffer_synchronize() {
// 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() {
if (plan_get_current_block() != NULL) { // Check if there are any blocks in the buffer.
sys_rt_exec_state.bit.cycleStart = true; // If so, execute them!
if (plan_get_current_block() != NULL) { // Check if there are any blocks in the buffer.
rtCycleStart = true; // If so, execute them!
}
}
@@ -250,176 +250,279 @@ void protocol_execute_realtime() {
// 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!
void protocol_exec_rt_system() {
ExecAlarm alarm = sys_rt_exec_alarm; // Temp variable to avoid calling volatile multiple times.
if (alarm != ExecAlarm::None) { // Enter only if an alarm is pending
static void protocol_do_alarm() {
switch (sys_rt_exec_alarm) {
case ExecAlarm::None:
return;
// System alarm. Everything has shutdown by something that has gone severely wrong. Report
// the source of the error to the user. If critical, Grbl disables by entering an infinite
// loop until system reset/abort.
sys.state = State::Alarm; // Set system alarm state
report_alarm_message(alarm);
// Halt everything upon a critical event flag. Currently hard and soft limits flag this.
if ((alarm == ExecAlarm::HardLimit) || (alarm == ExecAlarm::SoftLimit)) {
case ExecAlarm::HardLimit:
case ExecAlarm::SoftLimit:
sys.state = State::Alarm; // Set system alarm state
report_alarm_message(sys_rt_exec_alarm);
report_feedback_message(Message::CriticalEvent);
sys_rt_exec_state.bit.reset = false; // Disable any existing reset
rtReset = false; // Disable any existing reset
do {
// 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.
} while (!sys_rt_exec_state.bit.reset);
}
sys_rt_exec_alarm = ExecAlarm::None;
} while (!rtReset);
break;
default:
sys.state = State::Alarm; // Set system alarm state
report_alarm_message(sys_rt_exec_alarm);
break;
}
ExecState rt_exec_state;
rt_exec_state.value = sys_rt_exec_state.value; // Copy volatile sys_rt_exec_state.
if (rt_exec_state.value != 0 || cycle_stop) { // Test if any bits are on
// Execute system abort.
if (rt_exec_state.bit.reset) {
sys.abort = true; // Only place this is set true.
return; // Nothing else to do but exit.
}
// Execute and serial print status
if (rt_exec_state.bit.statusReport) {
report_realtime_status(CLIENT_ALL);
sys_rt_exec_state.bit.statusReport = false;
}
// NOTE: Once hold is initiated, the system immediately enters a suspend state to block all
// main program processes until either reset or resumed. This ensures a hold completes safely.
if (rt_exec_state.bit.motionCancel || rt_exec_state.bit.feedHold || rt_exec_state.bit.safetyDoor || rt_exec_state.bit.sleep) {
// State check for allowable states for hold methods.
if (!(sys.state == State::Alarm || sys.state == State::CheckMode)) {
// If in CYCLE or JOG states, immediately initiate a motion HOLD.
if (sys.state == State::Cycle || sys.state == State::Jog) {
if (!(sys.suspend.bit.motionCancel || sys.suspend.bit.jogCancel)) { // Block, if already holding.
st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration.
sys.step_control = {};
sys.step_control.executeHold = true; // Initiate suspend state with active flag.
if (sys.state == State::Jog) { // Jog cancelled upon any hold event, except for sleeping.
if (!rt_exec_state.bit.sleep) {
sys.suspend.bit.jogCancel = true;
}
}
}
}
// If IDLE, Grbl is not in motion. Simply indicate suspend state and hold is complete.
if (sys.state == State::Idle) {
sys.suspend.value = 0;
sys.suspend.bit.holdComplete = true;
}
// 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_state.bit.motionCancel) {
// MOTION_CANCEL only occurs during a CYCLE, but a HOLD and SAFETY_DOOR may been initiated beforehand
// to hold the CYCLE. Motion cancel is valid for a single planner block motion only, while jog cancel
// will handle and clear multiple planner block motions.
if (sys.state != State::Jog) {
sys.suspend.bit.motionCancel = true; // NOTE: State is State::Cycle.
}
sys_rt_exec_state.bit.motionCancel = false;
}
// Execute a feed hold with deceleration, if required. Then, suspend system.
if (rt_exec_state.bit.feedHold) {
// Block SAFETY_DOOR, JOG, and SLEEP states from changing to HOLD state.
if (!(sys.state == State::SafetyDoor || sys.state == State::Jog || sys.state == State::Sleep)) {
sys.state = State::Hold;
}
sys_rt_exec_state.bit.feedHold = false;
}
// Execute a safety door stop with a feed hold 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.
if (rt_exec_state.bit.safetyDoor) {
report_feedback_message(Message::SafetyDoorAjar);
// If jogging, block safety door methods until jog cancel is complete. Just flag that it happened.
if (!(sys.suspend.bit.jogCancel)) {
// Check if the safety re-opened during a restore parking motion only. Ignore if
// already retracting, parked or in sleep state.
if (sys.state == State::SafetyDoor) {
if (sys.suspend.bit.initiateRestore) { // Actively restoring
sys_rt_exec_alarm = ExecAlarm::None;
}
static void protocol_start_holding() {
if (!(sys.suspend.bit.motionCancel || sys.suspend.bit.jogCancel)) { // Block, if already holding.
st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration.
sys.step_control = {};
sys.step_control.executeHold = true; // Initiate suspend state with active flag.
}
}
static void protocol_cancel_jogging() {
if (!sys.suspend.bit.motionCancel) {
sys.suspend.bit.jogCancel = true;
}
}
static void protocol_hold_complete() {
sys.suspend.value = 0;
sys.suspend.bit.holdComplete = true;
}
static void protocol_do_motion_cancel() {
// 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.
rtMotionCancel = false;
rtCycleStart = false; // Cancel any pending start
// MOTION_CANCEL only occurs during a CYCLE, but a HOLD and SAFETY_DOOR may have been initiated
// beforehand. Motion cancel affects only a single planner block motion, while jog cancel
// will handle and clear multiple planner block motions.
switch (sys.state) {
case State::Alarm:
case State::CheckMode:
return; // Do not set motionCancel
case State::Idle:
protocol_hold_complete();
break;
case State::Cycle:
protocol_start_holding();
break;
case State::Jog:
protocol_start_holding();
protocol_cancel_jogging();
// When jogging, we do not set motionCancel, hence return not break
return;
case State::Sleep:
case State::Hold:
case State::Homing:
case State::SafetyDoor:
break;
}
sys.suspend.bit.motionCancel = true;
}
static void protocol_do_feedhold() {
// Execute a feed hold with deceleration, if required. Then, suspend system.
rtFeedHold = false;
rtCycleStart = false; // Cancel any pending start
switch (sys.state) {
case State::Alarm:
case State::CheckMode:
case State::SafetyDoor:
case State::Sleep:
return; // Do not change the state to Hold
case State::Hold:
case State::Homing:
break;
case State::Idle:
protocol_hold_complete();
break;
case State::Cycle:
protocol_start_holding();
break;
case State::Jog:
protocol_start_holding();
protocol_cancel_jogging();
return; // Do not change the state to Hold
}
sys.state = State::Hold;
}
static void protocol_do_safety_door() {
// Execute a safety door stop with a feed hold 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.
rtCycleStart = false; // Cancel any pending start
report_feedback_message(Message::SafetyDoorAjar);
switch (sys.state) {
case State::Alarm:
case State::CheckMode:
case State::Sleep:
rtSafetyDoor = false;
return; // Do not change the state to SafetyDoor
case State::Hold:
break;
case State::Homing:
sys_rt_exec_alarm = ExecAlarm::HomingFailDoor;
break;
case State::SafetyDoor:
if (!sys.suspend.bit.jogCancel && sys.suspend.bit.initiateRestore) { // Actively restoring
#ifdef PARKING_ENABLE
// Set hold and reset appropriate control flags to restart parking sequence.
if (sys.step_control.executeSysMotion) {
st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration.
sys.step_control = {};
sys.step_control.executeHold = true;
sys.step_control.executeSysMotion = true;
sys.suspend.bit.holdComplete = false;
} // else NO_MOTION is active.
// Set hold and reset appropriate control flags to restart parking sequence.
if (sys.step_control.executeSysMotion) {
st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration.
sys.step_control = {};
sys.step_control.executeHold = true;
sys.step_control.executeSysMotion = true;
sys.suspend.bit.holdComplete = false;
} // else NO_MOTION is active.
#endif
sys.suspend.bit.retractComplete = false;
sys.suspend.bit.initiateRestore = false;
sys.suspend.bit.restoreComplete = false;
sys.suspend.bit.restartRetract = true;
}
}
if (sys.state != State::Sleep) {
sys.state = State::SafetyDoor;
}
sys_rt_exec_state.bit.safetyDoor = false;
}
// NOTE: This flag doesn't change when the door closes, unlike sys.state. Ensures any parking motions
// are executed if the door switch closes and the state returns to HOLD.
sys.suspend.bit.safetyDoorAjar = true;
sys.suspend.bit.retractComplete = false;
sys.suspend.bit.initiateRestore = false;
sys.suspend.bit.restoreComplete = false;
sys.suspend.bit.restartRetract = true;
}
break;
case State::Idle:
protocol_hold_complete();
break;
case State::Cycle:
protocol_start_holding();
break;
case State::Jog:
protocol_start_holding();
protocol_cancel_jogging();
break;
}
if (!sys.suspend.bit.jogCancel) {
// If jogging, leave the safety door event pending until the jog cancel completes
rtSafetyDoor = false;
sys.state = State::SafetyDoor;
}
// NOTE: This flag doesn't change when the door closes, unlike sys.state. Ensures any parking motions
// are executed if the door switch closes and the state returns to HOLD.
sys.suspend.bit.safetyDoorAjar = true;
}
static void protocol_do_sleep() {
rtSleep = false;
switch (sys.state) {
case State::Alarm:
sys.suspend.bit.retractComplete = true;
sys.suspend.bit.holdComplete = true;
break;
case State::Idle:
protocol_hold_complete();
break;
case State::Cycle:
case State::Jog:
protocol_start_holding();
// Unlike other hold events, sleep does not set jogCancel
break;
case State::CheckMode:
case State::Sleep:
case State::Hold:
case State::Homing:
case State::SafetyDoor:
break;
}
sys.state = State::Sleep;
}
static void protocol_do_initiate_cycle() {
// Start cycle only if queued motions exist in planner buffer and the motion is not canceled.
sys.step_control = {}; // Restore step control to normal operation
if (plan_get_current_block() && !sys.suspend.bit.motionCancel) {
sys.suspend.value = 0; // Break suspend state.
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.suspend.value = 0; // Break suspend state.
sys.state = State::Idle;
}
}
// The handlers for rtFeedHold, rtMotionCancel, and rtsDafetyDoor clear rtCycleStart to
// ensure that auto-cycle-start does not resume a hold without explicit user input.
static void protocol_do_cycle_start() {
rtCycleStart = false;
// Execute a cycle start by starting the stepper interrupt to begin executing the blocks in queue.
// Resume door state when parking motion has retracted and door has been closed.
switch (sys.state) {
case State::SafetyDoor:
if (!sys.suspend.bit.safetyDoorAjar) {
if (sys.suspend.bit.restoreComplete) {
sys.state = State::Idle; // Set to IDLE to immediately resume the cycle.
} else if (sys.suspend.bit.retractComplete) {
// Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR.
// NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and
// the retraction execution is complete, which implies the initial feed hold is not active. To
// restore normal operation, the restore procedures must be initiated by the following flag. Once,
// they are complete, it will call CYCLE_START automatically to resume and exit the suspend.
sys.suspend.bit.initiateRestore = true;
}
}
if (rt_exec_state.bit.sleep) {
if (sys.state == State::Alarm) {
sys.suspend.bit.retractComplete = true;
sys.suspend.bit.holdComplete = true;
}
sys.state = State::Sleep;
sys_rt_exec_state.bit.sleep = false;
}
}
// Execute a cycle start by starting the stepper interrupt to begin executing the blocks in queue.
if (rt_exec_state.bit.cycleStart) {
// 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_state.bit.feedHold || rt_exec_state.bit.motionCancel || rt_exec_state.bit.safetyDoor)) {
// Resume door state when parking motion has retracted and door has been closed.
if (sys.state == State::SafetyDoor && !(sys.suspend.bit.safetyDoorAjar)) {
if (sys.suspend.bit.restoreComplete) {
sys.state = State::Idle; // Set to IDLE to immediately resume the cycle.
} else if (sys.suspend.bit.retractComplete) {
// Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR.
// NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and
// the retraction execution is complete, which implies the initial feed hold is not active. To
// restore normal operation, the restore procedures must be initiated by the following flag. Once,
// they are complete, it will call CYCLE_START automatically to resume and exit the suspend.
sys.suspend.bit.initiateRestore = true;
}
}
// Cycle start only when IDLE or when a hold is complete and ready to resume.
if (sys.state == State::Idle || (sys.state == State::Hold && sys.suspend.bit.holdComplete)) {
if (sys.state == State::Hold && sys.spindle_stop_ovr.value) {
sys.spindle_stop_ovr.bit.restoreCycle = true; // Set to restore in suspend routine and cycle start after.
} else {
// Start cycle only if queued motions exist in planner buffer and the motion is not canceled.
sys.step_control = {}; // Restore step control to normal operation
if (plan_get_current_block() && !sys.suspend.bit.motionCancel) {
sys.suspend.value = 0; // Break suspend state.
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.suspend.value = 0; // Break suspend state.
sys.state = State::Idle;
}
}
break;
case State::Idle:
protocol_do_initiate_cycle();
break;
case State::Hold:
// Cycle start only when IDLE or when a hold is complete and ready to resume.
if (sys.suspend.bit.holdComplete) {
if (sys.spindle_stop_ovr.value) {
sys.spindle_stop_ovr.bit.restoreCycle = true; // Set to restore in suspend routine and cycle start after.
} else {
protocol_do_initiate_cycle();
}
}
sys_rt_exec_state.bit.cycleStart = false;
}
if (cycle_stop) {
break;
case State::Alarm:
case State::CheckMode:
case State::Sleep:
case State::Cycle:
case State::Homing:
case State::Jog:
break;
}
}
static void protocol_do_cycle_stop() {
rtCycleStop = false;
switch (sys.state) {
case State::Hold:
case State::SafetyDoor:
case State::Sleep:
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
// realtime command execution in the main program, ensuring that the planner re-plans safely.
// NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
// cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
// NOTE: cycle_stop is set by the stepper subsystem when a cycle or feed hold completes.
if ((sys.state == State::Hold || sys.state == State::SafetyDoor || sys.state == State::Sleep) && !(sys.soft_limit) &&
!(sys.suspend.bit.jogCancel)) {
// NOTE: rtCycleStop is set by the stepper subsystem when a cycle or feed hold completes.
if (!sys.soft_limit && !sys.suspend.bit.jogCancel) {
// Hold complete. Set to indicate ready to resume. Remain in HOLD or DOOR states until user
// has issued a resume command or reset.
plan_cycle_reinitialize();
@@ -428,28 +531,37 @@ void protocol_exec_rt_system() {
}
sys.step_control.executeHold = false;
sys.step_control.executeSysMotion = false;
} else {
// Motion complete. Includes CYCLE/JOG/HOMING states and jog cancel/motion cancel/soft limit events.
// NOTE: Motion and jog cancel both immediately return to idle after the hold completes.
if (sys.suspend.bit.jogCancel) { // For jog cancel, flush buffers and sync positions.
sys.step_control = {};
plan_reset();
st_reset();
gc_sync_position();
plan_sync_position();
}
if (sys.suspend.bit.safetyDoorAjar) { // Only occurs when safety door opens during jog.
sys.suspend.bit.jogCancel = false;
sys.suspend.bit.holdComplete = true;
sys.state = State::SafetyDoor;
} else {
sys.suspend.value = 0;
sys.state = State::Idle;
}
break;
}
cycle_stop = false;
}
// Fall through
case State::Alarm:
case State::CheckMode:
case State::Idle:
case State::Cycle:
case State::Homing:
case State::Jog:
// Motion complete. Includes CYCLE/JOG/HOMING states and jog cancel/motion cancel/soft limit events.
// NOTE: Motion and jog cancel both immediately return to idle after the hold completes.
if (sys.suspend.bit.jogCancel) { // For jog cancel, flush buffers and sync positions.
sys.step_control = {};
plan_reset();
st_reset();
gc_sync_position();
plan_sync_position();
}
if (sys.suspend.bit.safetyDoorAjar) { // Only occurs when safety door opens during jog.
sys.suspend.bit.jogCancel = false;
sys.suspend.bit.holdComplete = true;
sys.state = State::SafetyDoor;
} else {
sys.suspend.value = 0;
sys.state = State::Idle;
}
break;
}
}
static void protocol_execute_overrides() {
// Execute overrides.
if ((sys_rt_f_override != sys.f_override) || (sys_rt_r_override != sys.r_override)) {
sys.f_override = sys_rt_f_override;
@@ -464,7 +576,7 @@ void protocol_exec_rt_system() {
sys.step_control.updateSpindleRpm = true;
sys.spindle_speed_ovr = sys_rt_s_override;
sys.report_ovr_counter = 0; // Set to report change immediately
// If spinlde is on, tell it the rpm has been overridden
// If spindle is on, tell it the RPM has been overridden
if (gc_state.modal.spindle != SpindleState::Disable) {
spindle->set_rpm(gc_state.spindle_speed);
}
@@ -487,44 +599,366 @@ void protocol_exec_rt_system() {
// run state can be determined by checking the parser state.
if (sys_rt_exec_accessory_override.bit.coolantFloodOvrToggle) {
sys_rt_exec_accessory_override.bit.coolantFloodOvrToggle = false;
if (config->_coolant->hasFlood()) {
if (sys.state == State::Idle || sys.state == State::Cycle || sys.state == State::Hold) {
gc_state.modal.coolant.Flood = !gc_state.modal.coolant.Flood;
config->_coolant->set_state(gc_state.modal.coolant); // Report counter set in coolant_set_state().
}
#ifdef COOLANT_FLOOD_PIN
if (sys.state == State::Idle || sys.state == State::Cycle || sys.state == State::Hold) {
gc_state.modal.coolant.Flood = !gc_state.modal.coolant.Flood;
coolant_set_state(gc_state.modal.coolant); // Report counter set in coolant_set_state().
}
#endif
}
if (sys_rt_exec_accessory_override.bit.coolantMistOvrToggle) {
sys_rt_exec_accessory_override.bit.coolantMistOvrToggle = false;
if (config->_coolant->hasMist()) {
if (sys.state == State::Idle || sys.state == State::Cycle || sys.state == State::Hold) {
gc_state.modal.coolant.Mist = !gc_state.modal.coolant.Mist;
config->_coolant->set_state(gc_state.modal.coolant); // Report counter set in coolant_set_state().
}
#ifdef COOLANT_MIST_PIN
if (sys.state == State::Idle || sys.state == State::Cycle || sys.state == State::Hold) {
gc_state.modal.coolant.Mist = !gc_state.modal.coolant.Mist;
coolant_set_state(gc_state.modal.coolant); // Report counter set in coolant_set_state().
}
#endif
}
}
void protocol_exec_rt_system() {
protocol_do_alarm(); // If there is a hard or soft limit, this will block until rtReset is set
if (rtReset) {
if (sys.state == State::Homing) {
sys_rt_exec_alarm = ExecAlarm::HomingFailReset;
}
// Execute system abort.
sys.abort = true; // Only place this is set true.
return; // Nothing else to do but exit.
}
if (rtStatusReport) {
rtStatusReport = false;
report_realtime_status(CLIENT_ALL);
}
if (rtMotionCancel) {
protocol_do_motion_cancel();
}
if (rtFeedHold) {
protocol_do_feedhold();
}
if (rtSafetyDoor) {
protocol_do_safety_door();
}
if (rtSleep) {
protocol_do_sleep();
}
if (rtCycleStart) {
protocol_do_cycle_start();
}
if (rtCycleStop) {
protocol_do_cycle_stop();
}
protocol_execute_overrides();
#ifdef DEBUG
if (sys_rt_exec_debug) {
report_realtime_debug();
sys_rt_exec_debug = false;
report_realtime_debug();
}
#endif
// Reload step segment buffer
switch (sys.state) {
case State::Alarm:
case State::CheckMode:
case State::Idle:
case State::Sleep:
break;
case State::Cycle:
case State::Hold:
case State::SafetyDoor:
case State::Homing:
case State::Sleep:
case State::Jog:
st_prep_buffer();
break;
default:
break;
}
}
//
//void protocol_exec_rt_system() {
// ExecAlarm alarm = sys_rt_exec_alarm; // Temp variable to avoid calling volatile multiple times.
// if (alarm != ExecAlarm::None) { // Enter only if an alarm is pending
// // System alarm. Everything has shutdown by something that has gone severely wrong. Report
// // the source of the error to the user. If critical, Grbl disables by entering an infinite
// // loop until system reset/abort.
// sys.state = State::Alarm; // Set system alarm state
// report_alarm_message(alarm);
// // Halt everything upon a critical event flag. Currently hard and soft limits flag this.
// if ((alarm == ExecAlarm::HardLimit) || (alarm == ExecAlarm::SoftLimit)) {
// report_feedback_message(Message::CriticalEvent);
// rtReset = false; // Disable any existing reset
// do {
// // 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.
// } while (!sys_rt_exec_state.bit.reset);
// }
// sys_rt_exec_alarm = ExecAlarm::None;
// }
// ExecState rt_exec_state;
// rt_exec_state.value = sys_rt_exec_state.value; // Copy volatile sys_rt_exec_state.
// if (rt_exec_state.value != 0 || cycle_stop) { // Test if any bits are on
// // Execute system abort.
// if (rt_exec_state.bit.reset) {
// sys.abort = true; // Only place this is set true.
// return; // Nothing else to do but exit.
// }
// // Execute and serial print status
// if (rt_exec_state.bit.statusReport) {
// report_realtime_status(CLIENT_ALL);
// sys_rt_exec_state.bit.statusReport = false;
// }
// // NOTE: Once hold is initiated, the system immediately enters a suspend state to block all
// // main program processes until either reset or resumed. This ensures a hold completes safely.
// if (rt_exec_state.bit.motionCancel || rt_exec_state.bit.feedHold || rt_exec_state.bit.safetyDoor || rt_exec_state.bit.sleep) {
// // State check for allowable states for hold methods.
// if (!(sys.state == State::Alarm || sys.state == State::CheckMode)) {
// // If in CYCLE or JOG states, immediately initiate a motion HOLD.
// if (sys.state == State::Cycle || sys.state == State::Jog) {
// if (!(sys.suspend.bit.motionCancel || sys.suspend.bit.jogCancel)) { // Block, if already holding.
// st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration.
// sys.step_control = {};
// sys.step_control.executeHold = true; // Initiate suspend state with active flag.
// if (sys.state == State::Jog) { // Jog cancelled upon any hold event, except for sleeping.
// if (!rt_exec_state.bit.sleep) {
// sys.suspend.bit.jogCancel = true;
// }
// }
// }
// }
// // If IDLE, Grbl is not in motion. Simply indicate suspend state and hold is complete.
// if (sys.state == State::Idle) {
// sys.suspend.value = 0;
// sys.suspend.bit.holdComplete = true;
// }
// // 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_state.bit.motionCancel) {
// // MOTION_CANCEL only occurs during a CYCLE, but a HOLD and SAFETY_DOOR may been initiated beforehand
// // to hold the CYCLE. Motion cancel is valid for a single planner block motion only, while jog cancel
// // will handle and clear multiple planner block motions.
// if (sys.state != State::Jog) {
// sys.suspend.bit.motionCancel = true; // NOTE: State is State::Cycle.
// }
// sys_rt_exec_state.bit.motionCancel = false;
// }
// // Execute a feed hold with deceleration, if required. Then, suspend system.
// if (rt_exec_state.bit.feedHold) {
// // Block SAFETY_DOOR, JOG, and SLEEP states from changing to HOLD state.
// if (!(sys.state == State::SafetyDoor || sys.state == State::Jog || sys.state == State::Sleep)) {
// sys.state = State::Hold;
// }
// sys_rt_exec_state.bit.feedHold = false;
// }
// // Execute a safety door stop with a feed hold 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.
// if (rt_exec_state.bit.safetyDoor) {
// report_feedback_message(Message::SafetyDoorAjar);
// // If jogging, block safety door methods until jog cancel is complete. Just flag that it happened.
// if (!(sys.suspend.bit.jogCancel)) {
// // Check if the safety re-opened during a restore parking motion only. Ignore if
// // already retracting, parked or in sleep state.
// if (sys.state == State::SafetyDoor) {
// if (sys.suspend.bit.initiateRestore) { // Actively restoring
//#ifdef PARKING_ENABLE
// // Set hold and reset appropriate control flags to restart parking sequence.
// if (sys.step_control.executeSysMotion) {
// st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration.
// sys.step_control = {};
// sys.step_control.executeHold = true;
// sys.step_control.executeSysMotion = true;
// sys.suspend.bit.holdComplete = false;
// } // else NO_MOTION is active.
//#endif
// sys.suspend.bit.retractComplete = false;
// sys.suspend.bit.initiateRestore = false;
// sys.suspend.bit.restoreComplete = false;
// sys.suspend.bit.restartRetract = true;
// }
// }
// if (sys.state != State::Sleep) {
// sys.state = State::SafetyDoor;
// }
// sys_rt_exec_state.bit.safetyDoor = false;
// }
// // NOTE: This flag doesn't change when the door closes, unlike sys.state. Ensures any parking motions
// // are executed if the door switch closes and the state returns to HOLD.
// sys.suspend.bit.safetyDoorAjar = true;
// }
// }
// if (rt_exec_state.bit.sleep) {
// if (sys.state == State::Alarm) {
// sys.suspend.bit.retractComplete = true;
// sys.suspend.bit.holdComplete = true;
// }
// sys.state = State::Sleep;
// sys_rt_exec_state.bit.sleep = false;
// }
// }
// // Execute a cycle start by starting the stepper interrupt to begin executing the blocks in queue.
// if (rt_exec_state.bit.cycleStart) {
// // 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_state.bit.feedHold || rt_exec_state.bit.motionCancel || rt_exec_state.bit.safetyDoor)) {
// // Resume door state when parking motion has retracted and door has been closed.
// if (sys.state == State::SafetyDoor && !(sys.suspend.bit.safetyDoorAjar)) {
// if (sys.suspend.bit.restoreComplete) {
// sys.state = State::Idle; // Set to IDLE to immediately resume the cycle.
// } else if (sys.suspend.bit.retractComplete) {
// // Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR.
// // NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and
// // the retraction execution is complete, which implies the initial feed hold is not active. To
// // restore normal operation, the restore procedures must be initiated by the following flag. Once,
// // they are complete, it will call CYCLE_START automatically to resume and exit the suspend.
// sys.suspend.bit.initiateRestore = true;
// }
// }
// // Cycle start only when IDLE or when a hold is complete and ready to resume.
// if (sys.state == State::Idle || (sys.state == State::Hold && sys.suspend.bit.holdComplete)) {
// if (sys.state == State::Hold && sys.spindle_stop_ovr.value) {
// sys.spindle_stop_ovr.bit.restoreCycle = true; // Set to restore in suspend routine and cycle start after.
// } else {
// // Start cycle only if queued motions exist in planner buffer and the motion is not canceled.
// sys.step_control = {}; // Restore step control to normal operation
// if (plan_get_current_block() && !sys.suspend.bit.motionCancel) {
// sys.suspend.value = 0; // Break suspend state.
// 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.suspend.value = 0; // Break suspend state.
// sys.state = State::Idle;
// }
// }
// }
// }
// sys_rt_exec_state.bit.cycleStart = false;
// }
// if (cycle_stop) {
// // Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
// // realtime command execution in the main program, ensuring that the planner re-plans safely.
// // NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
// // cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
// // NOTE: cycle_stop is set by the stepper subsystem when a cycle or feed hold completes.
// if ((sys.state == State::Hold || sys.state == State::SafetyDoor || sys.state == State::Sleep) && !(sys.soft_limit) &&
// !(sys.suspend.bit.jogCancel)) {
// // Hold complete. Set to indicate ready to resume. Remain in HOLD or DOOR states until user
// // has issued a resume command or reset.
// plan_cycle_reinitialize();
// if (sys.step_control.executeHold) {
// sys.suspend.bit.holdComplete = true;
// }
// sys.step_control.executeHold = false;
// sys.step_control.executeSysMotion = false;
// } else {
// // Motion complete. Includes CYCLE/JOG/HOMING states and jog cancel/motion cancel/soft limit events.
// // NOTE: Motion and jog cancel both immediately return to idle after the hold completes.
// if (sys.suspend.bit.jogCancel) { // For jog cancel, flush buffers and sync positions.
// sys.step_control = {};
// plan_reset();
// st_reset();
// gc_sync_position();
// plan_sync_position();
// }
// if (sys.suspend.bit.safetyDoorAjar) { // Only occurs when safety door opens during jog.
// sys.suspend.bit.jogCancel = false;
// sys.suspend.bit.holdComplete = true;
// sys.state = State::SafetyDoor;
// } else {
// sys.suspend.value = 0;
// sys.state = State::Idle;
// }
// }
// cycle_stop = false;
// }
// }
// // Execute overrides.
// if ((sys_rt_f_override != sys.f_override) || (sys_rt_r_override != sys.r_override)) {
// sys.f_override = sys_rt_f_override;
// sys.r_override = sys_rt_r_override;
// sys.report_ovr_counter = 0; // Set to report change immediately
// plan_update_velocity_profile_parameters();
// plan_cycle_reinitialize();
// }
//
// // NOTE: Unlike motion overrides, spindle overrides do not require a planner reinitialization.
// if (sys_rt_s_override != sys.spindle_speed_ovr) {
// sys.step_control.updateSpindleRpm = true;
// sys.spindle_speed_ovr = sys_rt_s_override;
// sys.report_ovr_counter = 0; // Set to report change immediately
// // If spinlde is on, tell it the rpm has been overridden
// if (gc_state.modal.spindle != SpindleState::Disable) {
// spindle->set_rpm(gc_state.spindle_speed);
// }
// }
//
// if (sys_rt_exec_accessory_override.bit.spindleOvrStop) {
// sys_rt_exec_accessory_override.bit.spindleOvrStop = false;
// // Spindle stop override allowed only while in HOLD state.
// // NOTE: Report counters are set in spindle_set_state() when spindle stop is executed.
// if (sys.state == State::Hold) {
// if (sys.spindle_stop_ovr.value == 0) {
// sys.spindle_stop_ovr.bit.initiate = true;
// } else if (sys.spindle_stop_ovr.bit.enabled) {
// sys.spindle_stop_ovr.bit.restore = true;
// }
// }
// }
//
// // 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 (sys_rt_exec_accessory_override.bit.coolantFloodOvrToggle) {
// sys_rt_exec_accessory_override.bit.coolantFloodOvrToggle = false;
// if (config->_coolant->hasFlood()) {
// if (sys.state == State::Idle || sys.state == State::Cycle || sys.state == State::Hold) {
// gc_state.modal.coolant.Flood = !gc_state.modal.coolant.Flood;
// config->_coolant->set_state(gc_state.modal.coolant); // Report counter set in coolant_set_state().
// }
// }
// }
// if (sys_rt_exec_accessory_override.bit.coolantMistOvrToggle) {
// sys_rt_exec_accessory_override.bit.coolantMistOvrToggle = false;
// if (config->_coolant->hasMist()) {
// if (sys.state == State::Idle || sys.state == State::Cycle || sys.state == State::Hold) {
// gc_state.modal.coolant.Mist = !gc_state.modal.coolant.Mist;
// config->_coolant->set_state(gc_state.modal.coolant); // Report counter set in coolant_set_state().
// }
// }
// }
//
//#ifdef DEBUG
// if (sys_rt_exec_debug) {
// report_realtime_debug();
// sys_rt_exec_debug = false;
// }
//#endif
// // Reload step segment buffer
// switch (sys.state) {
// case State::Cycle:
// case State::Hold:
// case State::SafetyDoor:
// case State::Homing:
// case State::Sleep:
// case State::Jog:
// st_prep_buffer();
// break;
// default:
// break;
// }
//}
// Handles Grbl 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 Grbl resumes normal operation.
@@ -648,7 +1082,7 @@ static void protocol_exec_rt_suspend() {
}
// Allows resuming from parking/safety door. Actively checks if safety door is closed and ready to resume.
if (sys.state == State::SafetyDoor) {
if (!(system_check_safety_door_ajar())) {
if (!config->_control->system_check_safety_door_ajar()) {
sys.suspend.bit.safetyDoorAjar = false; // Reset door ajar flag to denote ready to resume.
}
}
@@ -702,8 +1136,8 @@ static void protocol_exec_rt_suspend() {
}
#endif
if (!sys.suspend.bit.restartRetract) {
sys.suspend.bit.restoreComplete = true;
sys_rt_exec_state.bit.cycleStart = true; // Set to resume program.
sys.suspend.bit.restoreComplete = true;
rtCycleStart = true; // Set to resume program.
}
}
}
@@ -732,7 +1166,7 @@ static void protocol_exec_rt_suspend() {
}
}
if (sys.spindle_stop_ovr.bit.restoreCycle) {
sys_rt_exec_state.bit.cycleStart = true; // Set to resume program.
rtCycleStart = true; // Set to resume program.
}
sys.spindle_stop_ovr.value = 0; // Clear stop override state
}

View File

@@ -578,6 +578,16 @@ void report_echo_line_received(char* line, uint8_t client) {
// float wco = returns the work coordinate offset
// bool wpos = true for work position compensation
void addPinReport(char* status, char pinLetter, bool& pinReportStarted) {
if (!pinReportStarted) {
strcat(status, "|Pn:");
pinReportStarted = true;
}
size_t pos = strlen(status);
status[pos] = pinLetter;
status[pos + 1] = '\0';
}
// Prints real-time data. This function grabs a real-time snapshot of the stepper subprogram
// and the actual location of the CNC machine. Users may change the following function to their
// specific needs, but the desired real-time data report must be as short as possible. This is
@@ -643,62 +653,23 @@ void report_realtime_status(uint8_t client) {
strcat(status, temp);
#endif
#ifdef REPORT_FIELD_PIN_STATE
AxisMask lim_pin_state = limits_get_state();
ControlPins ctrl_pin_state = system_control_get_state();
bool prb_pin_state = config->_probe->get_state();
if (lim_pin_state || ctrl_pin_state.value || prb_pin_state) {
strcat(status, "|Pn:");
if (prb_pin_state) {
strcat(status, "P");
}
if (lim_pin_state) {
auto n_axis = config->_axes->_numberAxis;
if (n_axis >= 1 && bit_istrue(lim_pin_state, bit(X_AXIS))) {
strcat(status, "X");
}
if (n_axis >= 2 && bit_istrue(lim_pin_state, bit(Y_AXIS))) {
strcat(status, "Y");
}
if (n_axis >= 3 && bit_istrue(lim_pin_state, bit(Z_AXIS))) {
strcat(status, "Z");
}
if (n_axis >= 4 && bit_istrue(lim_pin_state, bit(A_AXIS))) {
strcat(status, "A");
}
if (n_axis >= 5 && bit_istrue(lim_pin_state, bit(B_AXIS))) {
strcat(status, "B");
}
if (n_axis >= 6 && bit_istrue(lim_pin_state, bit(C_AXIS))) {
strcat(status, "C");
}
}
if (ctrl_pin_state.value) {
if (ctrl_pin_state.bit.safetyDoor) {
strcat(status, "D");
}
if (ctrl_pin_state.bit.reset) {
strcat(status, "R");
}
if (ctrl_pin_state.bit.feedHold) {
strcat(status, "H");
}
if (ctrl_pin_state.bit.cycleStart) {
strcat(status, "S");
}
if (ctrl_pin_state.bit.macro0) {
strcat(status, "0");
}
if (ctrl_pin_state.bit.macro1) {
strcat(status, "1");
}
if (ctrl_pin_state.bit.macro2) {
strcat(status, "2");
}
if (ctrl_pin_state.bit.macro3) {
strcat(status, "3");
AxisMask lim_pin_state = limits_get_state();
bool prb_pin_state = config->_probe->get_state();
bool pinReportStarted = false;
if (prb_pin_state) {
addPinReport(status, 'P', pinReportStarted);
}
if (lim_pin_state) {
auto n_axis = config->_axes->_numberAxis;
for (int i = 0; i < n_axis; i++) {
if (bit_istrue(lim_pin_state, bit(i))) {
addPinReport(status, "XYZABC"[i], pinReportStarted);
}
}
}
config->_control->report(status, pinReportStarted);
#endif
#ifdef REPORT_FIELD_WORK_COORD_OFFSET
if (sys.report_wco_counter > 0) {

View File

@@ -232,17 +232,17 @@ void execute_realtime_command(Cmd command, uint8_t client) {
report_realtime_status(client); // direct call instead of setting flag
break;
case Cmd::CycleStart:
sys_rt_exec_state.bit.cycleStart = true;
rtCycleStart = true;
break;
case Cmd::FeedHold:
sys_rt_exec_state.bit.feedHold = true;
rtFeedHold = true;
break;
case Cmd::SafetyDoor:
sys_rt_exec_state.bit.safetyDoor = true;
rtSafetyDoor = true;
break;
case Cmd::JogCancel:
if (sys.state == State::Jog) { // Block all other states from invoking motion cancel.
sys_rt_exec_state.bit.motionCancel = true;
rtMotionCancel = true;
}
break;
case Cmd::DebugReport:

View File

@@ -295,7 +295,7 @@ static void stepper_pulse_func() {
spindle->set_rpm(0);
}
}
cycle_stop = true;
rtCycleStop = true;
return; // Nothing to do but exit.
}
}

View File

@@ -29,10 +29,20 @@ system_t sys;
int32_t sys_position[MAX_N_AXIS]; // Real-time machine (aka home) position vector in steps.
int32_t sys_probe_position[MAX_N_AXIS]; // Last probe position in machine coordinates and steps.
volatile ProbeState sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR.
volatile ExecState sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks.
volatile ExecAlarm sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms.
volatile ExecAlarm sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms.
volatile ExecAccessory sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides.
volatile bool cycle_stop; // For state transitions, instead of bitflag
volatile bool rtStatusReport;
volatile bool rtCycleStart;
volatile bool rtFeedHold;
volatile bool rtReset;
volatile bool rtSafetyDoor;
volatile bool rtMotionCancel;
volatile bool rtSleep;
volatile bool rtCycleStop; // For state transitions, instead of bitflag
volatile bool rtButtonMacro0;
volatile bool rtButtonMacro1;
volatile bool rtButtonMacro2;
volatile bool rtButtonMacro3;
volatile void* sys_pl_data_inflight; // holds a plan_line_data_t while cartesian_to_motors has taken ownership of a line motion
#ifdef DEBUG
volatile bool sys_rt_exec_debug;
@@ -47,164 +57,24 @@ UserOutput::DigitalOutput* myDigitalOutputs[MaxUserDigitalPin];
xQueueHandle control_sw_queue; // used by control switch debouncing
bool debouncing = false; // debouncing in process
void system_ini() { // Renamed from system_init() due to conflict with esp32 files
// setup control inputs
if (ControlSafetyDoorPin->get().defined()) {
grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Door switch on pin %s", ControlSafetyDoorPin->getStringValue());
auto pin = ControlSafetyDoorPin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
if (ControlResetPin->get().defined()) {
grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Reset switch on pin %s", ControlResetPin->getStringValue());
auto pin = ControlResetPin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
if (ControlFeedHoldPin->get().defined()) {
grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Hold switch on pin %s", ControlFeedHoldPin->getStringValue());
auto pin = ControlFeedHoldPin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
if (ControlCycleStartPin->get().defined()) {
grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Start switch on pin %s", ControlCycleStartPin->getStringValue());
auto pin = ControlCycleStartPin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
if (MacroButton0Pin->get().defined()) {
auto pin = MacroButton0Pin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
if (MacroButton1Pin->get().defined()) {
auto pin = MacroButton1Pin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
if (MacroButton2Pin->get().defined()) {
auto pin = MacroButton2Pin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
if (MacroButton3Pin->get().defined()) {
auto pin = MacroButton3Pin->get();
auto attr = Pin::Attr::Input | Pin::Attr::ISR;
if (pin.capabilities().has(Pins::PinCapabilities::PullUp)) {
attr = attr | Pin::Attr::PullUp;
}
pin.setAttr(attr);
pin.attachInterrupt(isr_control_inputs, CHANGE);
}
#ifdef ENABLE_CONTROL_SW_DEBOUNCE
// setup task used for debouncing
control_sw_queue = xQueueCreate(10, sizeof(int));
xTaskCreate(controlCheckTask,
"controlCheckTask",
3096,
NULL,
5, // priority
NULL);
#endif
void init_output_pins() {
//customize pin definition if needed
#if (GRBL_SPI_SS != -1) || (GRBL_SPI_MISO != -1) || (GRBL_SPI_MOSI != -1) || (GRBL_SPI_SCK != -1)
SPI.begin(GRBL_SPI_SCK, GRBL_SPI_MISO, GRBL_SPI_MOSI, GRBL_SPI_SS);
#endif
auto userOutputs = config->_userOutputs;
// Setup M62,M63,M64,M65 pins
for (int i = 0; i < 4; ++i) {
myDigitalOutputs[i] = new UserOutput::DigitalOutput(i, UserDigitalPin[i]->get());
myDigitalOutputs[i] = new UserOutput::DigitalOutput(i, userOutputs->_digitalOutput[i]);
}
// Setup M67 Pins
myAnalogOutputs[0] = new UserOutput::AnalogOutput(0, UserAnalogPin[0]->get(), USER_ANALOG_PIN_0_FREQ);
myAnalogOutputs[1] = new UserOutput::AnalogOutput(1, UserAnalogPin[1]->get(), USER_ANALOG_PIN_1_FREQ);
myAnalogOutputs[2] = new UserOutput::AnalogOutput(2, UserAnalogPin[2]->get(), USER_ANALOG_PIN_2_FREQ);
myAnalogOutputs[3] = new UserOutput::AnalogOutput(3, UserAnalogPin[3]->get(), USER_ANALOG_PIN_3_FREQ);
}
#ifdef ENABLE_CONTROL_SW_DEBOUNCE
// this is the debounce task
void controlCheckTask(void* pvParameters) {
while (true) {
std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // read fence for settings and other state
int evt;
xQueueReceive(control_sw_queue, &evt, portMAX_DELAY); // block until receive queue
vTaskDelay(CONTROL_SW_DEBOUNCE_PERIOD); // delay a while
ControlPins pins = system_control_get_state();
if (pins.value) {
system_exec_control_pin(pins);
}
debouncing = false;
static UBaseType_t uxHighWaterMark = 0;
# ifdef DEBUG_TASK_STACK
reportTaskStackSize(uxHighWaterMark);
# endif
}
}
#endif
void IRAM_ATTR isr_control_inputs(void*) {
#ifdef ENABLE_CONTROL_SW_DEBOUNCE
// we will start a task that will recheck the switches after a small delay
int evt;
if (!debouncing) { // prevent resending until debounce is done
debouncing = true;
xQueueSendFromISR(control_sw_queue, &evt, NULL);
}
#else
ControlPins pins = system_control_get_state();
system_exec_control_pin(pins);
#endif
}
// Returns if safety door is ajar(T) or closed(F), based on pin state.
uint8_t system_check_safety_door_ajar() {
#ifdef ENABLE_SAFETY_DOOR_INPUT_PIN
return system_control_get_state().bit.safetyDoor;
#else
return false; // Input pin not enabled, so just return that it's closed.
#endif
myAnalogOutputs[0] = new UserOutput::AnalogOutput(0, userOutputs->_analogOutput[0], USER_ANALOG_PIN_0_FREQ);
myAnalogOutputs[1] = new UserOutput::AnalogOutput(1, userOutputs->_analogOutput[1], USER_ANALOG_PIN_1_FREQ);
myAnalogOutputs[2] = new UserOutput::AnalogOutput(2, userOutputs->_analogOutput[2], USER_ANALOG_PIN_2_FREQ);
myAnalogOutputs[3] = new UserOutput::AnalogOutput(3, userOutputs->_analogOutput[3], USER_ANALOG_PIN_3_FREQ);
}
void system_flag_wco_change() {
@@ -236,85 +106,6 @@ float* system_get_mpos() {
return position;
};
// Returns control pin state as a uint8 bitfield. Each bit indicates the input pin state, where
// triggered is 1 and not triggered is 0. Invert mask is applied. Bitfield organization is
// defined by the ControlPin in System.h.
ControlPins system_control_get_state() {
ControlPins defined_pins;
defined_pins.value = 0;
ControlPins pin_states;
pin_states.value = 0;
defined_pins.bit.safetyDoor = ControlSafetyDoorPin->get().defined();
if (ControlSafetyDoorPin->get().read()) {
pin_states.bit.safetyDoor = true;
}
defined_pins.bit.reset = ControlResetPin->get().defined();
if (ControlResetPin->get().read()) {
pin_states.bit.reset = true;
}
defined_pins.bit.feedHold = ControlFeedHoldPin->get().defined();
if (ControlFeedHoldPin->get().read()) {
pin_states.bit.feedHold = true;
}
defined_pins.bit.cycleStart = ControlCycleStartPin->get().defined();
if (ControlCycleStartPin->get().read()) {
pin_states.bit.cycleStart = true;
}
defined_pins.bit.macro0 = MacroButton0Pin->get().defined();
if (MacroButton0Pin->get().read()) {
pin_states.bit.macro0 = true;
}
defined_pins.bit.macro1 = MacroButton1Pin->get().defined();
if (MacroButton1Pin->get().read()) {
pin_states.bit.macro1 = true;
}
defined_pins.bit.macro2 = MacroButton2Pin->get().defined();
if (MacroButton2Pin->get().read()) {
pin_states.bit.macro2 = true;
}
defined_pins.bit.macro3 = MacroButton3Pin->get().defined();
if (MacroButton3Pin->get().read()) {
pin_states.bit.macro3 = true;
}
#ifdef INVERT_CONTROL_PIN_MASK
pin_states.value ^= (INVERT_CONTROL_PIN_MASK & defined_pins.value);
#endif
return pin_states;
}
// TODO: make this work with Control.cpp
// execute the function of the control pin
void system_exec_control_pin(ControlPins pins) {
if (pins.bit.reset) {
grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "Reset via control pin");
mc_reset();
} else if (pins.bit.cycleStart) {
sys_rt_exec_state.bit.cycleStart = true;
} else if (pins.bit.feedHold) {
sys_rt_exec_state.bit.feedHold = true;
} else if (pins.bit.safetyDoor) {
sys_rt_exec_state.bit.safetyDoor = true;
} else if (pins.bit.macro0) {
user_defined_macro(0); // function must be implemented by user
} else if (pins.bit.macro1) {
user_defined_macro(1); // function must be implemented by user
} else if (pins.bit.macro2) {
user_defined_macro(2); // function must be implemented by user
} else if (pins.bit.macro3) {
user_defined_macro(3); // function must be implemented by user
}
}
void sys_digital_all_off() {
for (uint8_t io_num = 0; io_num < MaxUserDigitalPin; io_num++) {
myDigitalOutputs[io_num]->set_level(LOW);

View File

@@ -114,48 +114,34 @@ typedef struct {
} system_t;
extern system_t sys;
// Control pin states
struct ControlPinBits {
uint8_t safetyDoor : 1;
uint8_t reset : 1;
uint8_t feedHold : 1;
uint8_t cycleStart : 1;
uint8_t macro0 : 1;
uint8_t macro1 : 1;
uint8_t macro2 : 1;
uint8_t macro3 : 1;
};
union ControlPins {
uint8_t value;
ControlPinBits bit;
};
// NOTE: These position variables may need to be declared as volatiles, if problems arise.
extern int32_t sys_position[MAX_N_AXIS]; // Real-time machine (aka home) position vector in steps.
extern int32_t sys_probe_position[MAX_N_AXIS]; // Last probe position in machine coordinates and steps.
extern volatile ProbeState sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR.
extern volatile ExecState sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks.
extern volatile ExecAlarm sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms.
extern volatile ExecAccessory sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides.
extern volatile Percent sys_rt_f_override; // Feed override value in percent
extern volatile Percent sys_rt_r_override; // Rapid feed override value in percent
extern volatile Percent sys_rt_s_override; // Spindle override value in percent
extern volatile bool cycle_stop;
extern volatile bool rtStatusReport;
extern volatile bool rtCycleStart;
extern volatile bool rtFeedHold;
extern volatile bool rtReset;
extern volatile bool rtSafetyDoor;
extern volatile bool rtMotionCancel;
extern volatile bool rtSleep;
extern volatile bool rtCycleStop;
extern volatile bool rtButtonMacro0;
extern volatile bool rtButtonMacro1;
extern volatile bool rtButtonMacro2;
extern volatile bool rtButtonMacro3;
extern volatile void* sys_pl_data_inflight; // holds a plan_line_data_t while cartesian_to_motors has taken ownership of a line motion
#ifdef DEBUG
extern volatile bool sys_rt_exec_debug;
#endif
void system_ini(); // Renamed from system_init() due to conflict with esp32 files
// Returns bitfield of control pin states, organized by CONTROL_PIN_INDEX. (1=triggered, 0=not triggered).
ControlPins system_control_get_state();
// Returns if safety door is ajar(T) or closed(F), based on pin state.
uint8_t system_check_safety_door_ajar();
void isr_control_inputs(void*);
void init_output_pins(); // Renamed from system_init() due to conflict with esp32 files
// Execute the startup script lines stored in non-volatile storage upon initialization
void system_execute_startup(char* line);
@@ -172,10 +158,6 @@ float system_convert_axis_steps_to_mpos(int32_t* steps, uint8_t idx);
void system_convert_array_steps_to_mpos(float* position, int32_t* steps);
float* system_get_mpos();
// A task that runs after a control switch interrupt for debouncing.
void controlCheckTask(void* pvParameters);
void system_exec_control_pin(ControlPins pins);
bool sys_set_digital(uint8_t io_num, bool turnOn);
void sys_digital_all_off();
bool sys_set_analog(uint8_t io_num, float percent);