diff --git a/FluidNC/src/Jog.cpp b/FluidNC/src/Jog.cpp index f50844897..7dc23136c 100644 --- a/FluidNC/src/Jog.cpp +++ b/FluidNC/src/Jog.cpp @@ -12,6 +12,10 @@ // Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. // cancelledInflight will be set to true if was not added to parser due to a cancelJog. Error jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block, bool* cancelledInflight) { + if (config->_kinematics->invalid_line(gc_block->values.xyz)) { + config->_kinematics->constrain_line(gc_block->values.xyz, gc_state.position); + } + // Initialize planner data struct for jogging motions. // NOTE: Spindle and coolant are allowed to fully function with overrides during a jog. pl_data->feed_rate = gc_block->values.f; @@ -19,8 +23,6 @@ Error jog_execute(plan_line_data_t* pl_data, parser_block_t* gc_block, bool* can pl_data->is_jog = true; pl_data->line_number = gc_block->values.n; - constrainToSoftLimits(gc_block->values.xyz); - if (!mc_linear(gc_block->values.xyz, pl_data, gc_state.position)) { return Error::JogCancelled; } diff --git a/FluidNC/src/Kinematics/Cartesian.cpp b/FluidNC/src/Kinematics/Cartesian.cpp index 359c1baf6..8735988e9 100644 --- a/FluidNC/src/Kinematics/Cartesian.cpp +++ b/FluidNC/src/Kinematics/Cartesian.cpp @@ -18,6 +18,234 @@ namespace Kinematics { } } + // Check that the arc does not exceed the soft limits using a fast + // algorithm that requires no transcendental functions. + // caxes[] depends on the plane selection via G17, G18, and G19. caxes[0] is the first + // circle plane axis, caxes[1] is the second circle plane axis, and caxes[2] is the + // orthogonal plane. So for G17 mode, caxes[] is { 0, 1, 2} for { X, Y, Z}. G18 is {2, 0, 1} i.e. {Z, X, Y}, and G19 is {1, 2, 0} i.e. {Y, Z, X} + bool Cartesian::invalid_arc(float* target, float* position, float center[3], float radius, size_t caxes[3], bool is_clockwise_arc) { + auto axes = config->_axes; + + // Handle the orthognal axis first to get it out of the way. + size_t the_axis = caxes[2]; + if (axes->_axis[the_axis]->_softLimits) { + float amin = std::min(position[the_axis], target[the_axis]); + if (amin < limitsMinPosition(the_axis)) { + limit_error(the_axis, amin); + return true; + } + float amax = std::max(position[the_axis], target[the_axis]); + if (amax > limitsMaxPosition(the_axis)) { + limit_error(the_axis, amax); + return true; + } + } + + bool limited[2] = { axes->_axis[caxes[0]]->_softLimits, axes->_axis[caxes[1]]->_softLimits }; + + // If neither axis of the circular plane has limits enabled, skip the computation + if (!(limited[0] || limited[1])) { + return false; + } + + // The origin for this calculation's coordinate system is at the center of the arc. + // The 0 and 1 entries are for the circle plane + // and the 2 entry is the orthogonal (linear) direction + + float s[2], e[2]; // Start and end of arc in the circle plane, relative to center + + // Depending on the arc direction, set the arc start and end points relative + // to the arc center. Afterwards, end is always counterclockwise relative to + // start, thus simplifying the following decision tree. + if (is_clockwise_arc) { + s[0] = target[caxes[0]] - center[0]; + s[1] = target[caxes[1]] - center[1]; + e[0] = position[caxes[0]] - center[0]; + e[1] = position[caxes[1]] - center[1]; + } else { + s[0] = position[caxes[0]] - center[0]; + s[1] = position[caxes[1]] - center[1]; + e[0] = target[caxes[0]] - center[0]; + e[1] = target[caxes[1]] - center[1]; + } + + // Axis crossings - plus and minus caxes[0] and caxes[1] + bool p[2] = { false, false }; + bool m[2] = { false, false }; + + // The following decision tree determines whether the arc crosses + // the horizontal and vertical axes of the circular plane in the + // positive and negative half planes. There are ways to express + // it in fewer lines of code by converting to alternate + // representations like angles, but this way is computationally + // efficient since it avoids any use of transcendental functions. + // Every path through this decision tree is either 4 or 5 simple + // comparisons. + if (e[1] >= 0) { // End in upper half plane + if (e[0] > 0) { // End in quadrant 0 - X+ Y+ + if (s[1] >= 0) { // Start in upper half plane + if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ + if (s[0] <= e[0]) { // wraparound + p[0] = p[1] = m[0] = m[1] = true; + } + } else { // Start in quadrant 1 - X- Y+ + m[0] = m[1] = p[0] = true; + } + } else { // Start in lower half plane + if (s[0] > 0) { // Start in quadrant 3 - X+ Y- + p[0] = true; + } else { // Start in quadrant 2 - X- Y- + m[1] = p[0] = true; + } + } + } else { // End in quadrant 1 - X- Y+ + if (s[1] >= 0) { // Start in upper half plane + if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ + p[1] = true; + } else { // Start in quadrant 1 - X- Y+ + if (s[0] <= e[0]) { // wraparound + p[0] = p[1] = m[0] = m[1] = true; + } + } + } else { // Start in lower half plane + if (s[0] > 0) { // Start in quadrant 3 - X+ Y- + p[0] = p[1] = true; + } else { // Start in quadrant 2 - X- Y- + m[1] = p[0] = p[1] = true; + } + } + } + } else { // e[1] < 0 - end in lower half plane + if (e[0] > 0) { // End in quadrant 3 - X+ Y+ + if (s[1] >= 0) { // Start in upper half plane + if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ + p[1] = m[0] = m[1] = true; + } else { // Start in quadrant 1 - X- Y+ + m[0] = m[1] = true; + } + } else { // Start in lower half plane + if (s[0] > 0) { // Start in quadrant 3 - X+ Y- + if (s[0] >= e[0]) { // wraparound + p[0] = p[1] = m[0] = m[1] = true; + } + } else { // Start in quadrant 2 - X- Y- + m[1] = true; + } + } + } else { // End in quadrant 2 - X- Y+ + if (s[1] >= 0) { // Start in upper half plane + if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ + p[1] = m[0] = true; + } else { // Start in quadrant 1 - X- Y+ + m[0] = true; + } + } else { // Start in lower half plane + if (s[0] > 0) { // Start in quadrant 3 - X+ Y- + p[0] = p[1] = m[0] = true; + } else { // Start in quadrant 2 - X- Y- + if (s[0] >= e[0]) { // wraparound + p[0] = p[1] = m[0] = m[1] = true; + } + } + } + } + } + // Now check limits based on arc endpoints and axis crossings + for (size_t a = 0; a < 2; ++a) { + the_axis = caxes[a]; + if (limited[a]) { + // If we crossed the axis in the positive half plane, the + // maximum extent along that axis is at center + radius. + // Otherwise it is the maximum coordinate of the start and + // end positions. Similarly for the negative half plane + // and the minimum extent. + float amin = m[a] ? center[a] - radius : std::min(target[the_axis], position[the_axis]); + if (amin < limitsMinPosition(the_axis)) { + limit_error(the_axis, amin); + return true; + } + float amax = p[a] ? center[a] + radius : std::max(target[the_axis], position[the_axis]); + if (amax > limitsMaxPosition(the_axis)) { + limit_error(the_axis, amax); + return true; + } + } + } + return false; + } + + void Cartesian::constrain_line(float* target, float* position) { + auto axes = config->_axes; + auto n_axis = config->_axes->_numberAxis; + + float* current_position = get_mpos(); + MotorMask lim_pin_state = limits_get_state(); + + for (int axis = 0; axis < n_axis; axis++) { + auto axisSetting = axes->_axis[axis]; + // If the axis is moving from the current location and soft limits are on. + if (axisSetting->_softLimits && target[axis] != current_position[axis]) { + // When outside the axis range, only small nudges to clear switches are allowed + bool move_positive = target[axis] > current_position[axis]; + if ((!move_positive && (current_position[axis] < limitsMinPosition(axis))) || + (move_positive && (current_position[axis] > limitsMaxPosition(axis)))) { + // only allow a nudge if a switch is active + if (bitnum_is_false(lim_pin_state, Machine::Axes::motor_bit(axis, 0)) && + bitnum_is_false(lim_pin_state, Machine::Axes::motor_bit(axis, 1))) { + target[axis] = current_position[axis]; // cancel the move on this axis + log_debug("Soft limit violation on " << Machine::Axes::_names[axis]); + continue; + } + float jog_dist = target[axis] - current_position[axis]; + + MotorMask axisMotors = Machine::Axes::axes_to_motors(1 << axis); + bool posLimited = bits_are_true(Machine::Axes::posLimitMask, axisMotors); + bool negLimited = bits_are_true(Machine::Axes::negLimitMask, axisMotors); + + // if jog is positive and only the positive switch is active, then kill the move + // if jog is negative and only the negative switch is active, then kill the move + if (posLimited != negLimited) { // XOR, because ambiguous (both) is OK + if ((negLimited && (jog_dist < 0)) || (posLimited && (jog_dist > 0))) { + target[axis] = current_position[axis]; // cancel the move on this axis + log_debug("Jog into active switch blocked on " << Machine::Axes::_names[axis]); + continue; + } + } + + auto nudge_max = axisSetting->_motors[0]->_pulloff; + if (abs(jog_dist) > nudge_max) { + target[axis] = (jog_dist >= 0) ? current_position[axis] + nudge_max : current_position[axis] + nudge_max; + log_debug("Jog amount limited when outside soft limits") + } + continue; + } + + if (target[axis] < limitsMinPosition(axis)) { + target[axis] = limitsMinPosition(axis); + } else if (target[axis] > limitsMaxPosition(axis)) { + target[axis] = limitsMaxPosition(axis); + } else { + continue; + } + log_debug("Jog constrained to axis range"); + } + } + } + + bool Cartesian::invalid_line(float* cartesian) { + auto axes = config->_axes; + auto n_axis = config->_axes->_numberAxis; + + for (int axis = 0; axis < n_axis; axis++) { + float coordinate = cartesian[axis]; + if (axes->_axis[axis]->_softLimits && (coordinate < limitsMinPosition(axis) || coordinate > limitsMaxPosition(axis))) { + limit_error(axis, coordinate); + return true; + } + } + return false; + } + bool Cartesian::cartesian_to_motors(float* target, plan_line_data_t* pl_data, float* position) { // Motor space is cartesian space, so we do no transform. return mc_move_motors(target, pl_data); diff --git a/FluidNC/src/Kinematics/Cartesian.h b/FluidNC/src/Kinematics/Cartesian.h index 50476241a..2b026cd54 100644 --- a/FluidNC/src/Kinematics/Cartesian.h +++ b/FluidNC/src/Kinematics/Cartesian.h @@ -16,13 +16,17 @@ namespace Kinematics { public: Cartesian() = default; - Cartesian(const Cartesian&) = delete; - Cartesian(Cartesian&&) = delete; + Cartesian(const Cartesian&) = delete; + Cartesian(Cartesian&&) = delete; Cartesian& operator=(const Cartesian&) = delete; - Cartesian& operator=(Cartesian&&) = delete; + Cartesian& operator=(Cartesian&&) = delete; // Kinematic Interface + virtual void constrain_line(float* cartesian, float* position) override; + virtual bool invalid_line(float* cartesian) override; + virtual bool invalid_arc(float* target, float* position, float center[3], float radius, size_t caxes[3], bool is_clockwise_arc) override; + virtual bool cartesian_to_motors(float* target, plan_line_data_t* pl_data, float* position) override; virtual void init() override; virtual void init_position() override; diff --git a/FluidNC/src/Kinematics/Kinematics.cpp b/FluidNC/src/Kinematics/Kinematics.cpp index 1a186f7d0..cb30a5ae9 100644 --- a/FluidNC/src/Kinematics/Kinematics.cpp +++ b/FluidNC/src/Kinematics/Kinematics.cpp @@ -8,6 +8,21 @@ #include "Cartesian.h" namespace Kinematics { + void Kinematics::constrain_line(float* target, float* position) { + Assert(_system != nullptr, "No kinematic system"); + return _system->constrain_line(target, position); + } + + bool Kinematics::invalid_line(float* target) { + Assert(_system != nullptr, "No kinematic system"); + return _system->invalid_line(target); + } + + bool Kinematics::invalid_arc(float* target, float* position, float center[3], float radius, size_t caxes[3], bool is_clockwise_arc) { + Assert(_system != nullptr, "No kinematic system"); + return _system->invalid_arc(target, position, center, radius, caxes, is_clockwise_arc); + } + bool Kinematics::cartesian_to_motors(float* target, plan_line_data_t* pl_data, float* position) { Assert(_system != nullptr, "No kinematic system"); return _system->cartesian_to_motors(target, pl_data, position); diff --git a/FluidNC/src/Kinematics/Kinematics.h b/FluidNC/src/Kinematics/Kinematics.h index e8ea30352..9add47c4c 100644 --- a/FluidNC/src/Kinematics/Kinematics.h +++ b/FluidNC/src/Kinematics/Kinematics.h @@ -48,6 +48,10 @@ namespace Kinematics { void motors_to_cartesian(float* cartesian, float* motors, int n_axis); void transform_cartesian_to_motors(float* motors, float* cartesian); + void constrain_line(float* target, float* position); + bool invalid_line(float* target); + bool invalid_arc(float* target, float* position, float center[3], float radius, size_t caxes[3], bool is_clockwise_arc); + bool canHome(AxisMask axisMask); void releaseMotors(AxisMask axisMask, MotorMask motors); bool limitReached(AxisMask& axisMask, MotorMask& motors, MotorMask limited); @@ -60,15 +64,22 @@ namespace Kinematics { public: KinematicSystem() = default; - KinematicSystem(const KinematicSystem&) = delete; - KinematicSystem(KinematicSystem&&) = delete; + KinematicSystem(const KinematicSystem&) = delete; + KinematicSystem(KinematicSystem&&) = delete; KinematicSystem& operator=(const KinematicSystem&) = delete; - KinematicSystem& operator=(KinematicSystem&&) = delete; + KinematicSystem& operator=(KinematicSystem&&) = delete; // Kinematic system interface. virtual bool cartesian_to_motors(float* target, plan_line_data_t* pl_data, float* position) = 0; virtual void init() = 0; - virtual void init_position() = 0; // used to set the machine position at init + virtual void init_position() = 0; // used to set the machine position at init + + virtual void constrain_line(float* cartesian, float* position) {} + virtual bool invalid_line(float* cartesian) { return false; } + virtual bool invalid_arc(float* target, float* position, float center[3], float radius, size_t caxes[3], bool is_clockwise_arc) { + return false; + } + virtual void motors_to_cartesian(float* cartesian, float* motors, int n_axis) = 0; virtual void transform_cartesian_to_motors(float* motors, float* cartesian) = 0; diff --git a/FluidNC/src/Limits.cpp b/FluidNC/src/Limits.cpp index 9d3200411..c8fc0d74e 100644 --- a/FluidNC/src/Limits.cpp +++ b/FluidNC/src/Limits.cpp @@ -54,65 +54,6 @@ bool ambiguousLimit() { bool soft_limit = false; -// Constrain the coordinates to stay within the soft limit envelope -void constrainToSoftLimits(float* cartesian) { - auto axes = config->_axes; - auto n_axis = config->_axes->_numberAxis; - - float* current_position = get_mpos(); - MotorMask lim_pin_state = limits_get_state(); - - for (int axis = 0; axis < n_axis; axis++) { - auto axisSetting = axes->_axis[axis]; - // If the axis is moving from the current location and soft limits are on. - if (axisSetting->_softLimits && cartesian[axis] != current_position[axis]) { - // When outside the axis range, only small nudges to clear switches are allowed - bool move_positive = cartesian[axis] > current_position[axis]; - if ((!move_positive && (current_position[axis] < limitsMinPosition(axis))) || - (move_positive && (current_position[axis] > limitsMaxPosition(axis)))) { - // only allow a nudge if a switch is active - if (bitnum_is_false(lim_pin_state, Machine::Axes::motor_bit(axis, 0)) && - bitnum_is_false(lim_pin_state, Machine::Axes::motor_bit(axis, 1))) { - cartesian[axis] = current_position[axis]; // cancel the move on this axis - log_debug("Soft limit violation on " << Machine::Axes::_names[axis]); - continue; - } - float jog_dist = cartesian[axis] - current_position[axis]; - - MotorMask axisMotors = Machine::Axes::axes_to_motors(1 << axis); - bool posLimited = bits_are_true(Machine::Axes::posLimitMask, axisMotors); - bool negLimited = bits_are_true(Machine::Axes::negLimitMask, axisMotors); - - // if jog is positive and only the positive switch is active, then kill the move - // if jog is negative and only the negative switch is active, then kill the move - if (posLimited != negLimited) { // XOR, because ambiguous (both) is OK - if ((negLimited && (jog_dist < 0)) || (posLimited && (jog_dist > 0))) { - cartesian[axis] = current_position[axis]; // cancel the move on this axis - log_debug("Jog into active switch blocked on " << Machine::Axes::_names[axis]); - continue; - } - } - - auto nudge_max = axisSetting->_motors[0]->_pulloff; - if (abs(jog_dist) > nudge_max) { - cartesian[axis] = (jog_dist >= 0) ? current_position[axis] + nudge_max : current_position[axis] + nudge_max; - log_debug("Jog amount limited when outside soft limits") - } - continue; - } - - if (cartesian[axis] < limitsMinPosition(axis)) { - cartesian[axis] = limitsMinPosition(axis); - } else if (cartesian[axis] > limitsMaxPosition(axis)) { - cartesian[axis] = limitsMaxPosition(axis); - } else { - continue; - } - log_debug("Jog constrained to axis range"); - } - } -} - // Performs a soft limit check. Called from mcline() only. Assumes the machine has been homed, // the workspace volume is in all negative space, and the system is in normal operation. // NOTE: Used by jogging to limit travel within soft-limit volume. @@ -134,20 +75,6 @@ void limit_error(size_t axis, float coordinate) { } while (sys.state != State::Idle); } mc_critical(ExecAlarm::SoftLimit); - log_debug("Soft limits"); -} - -void limits_soft_check(float* cartesian) { - auto axes = config->_axes; - auto n_axis = config->_axes->_numberAxis; - - for (int axis = 0; axis < n_axis; axis++) { - float coordinate = cartesian[axis]; - if (axes->_axis[axis]->_softLimits && (coordinate < limitsMinPosition(axis) || coordinate > limitsMaxPosition(axis))) { - limit_error(axis, coordinate); - return; - } - } } float limitsMaxPosition(size_t axis) { diff --git a/FluidNC/src/Limits.h b/FluidNC/src/Limits.h index 107a875c3..0c2ef2d11 100644 --- a/FluidNC/src/Limits.h +++ b/FluidNC/src/Limits.h @@ -4,7 +4,7 @@ #pragma once -#include "System.h" // AxisMask +#include "System.h" #include @@ -18,12 +18,6 @@ MotorMask limits_get_state(); void limit_error(size_t axis, float cordinate); -// Check for soft limit violations -void limits_soft_check(float* cartesian); - -// Constrain the coordinates to stay within the soft limit envelope -void constrainToSoftLimits(float* cartesian); - float limitsMaxPosition(size_t axis); float limitsMinPosition(size_t axis); diff --git a/FluidNC/src/Machine/Axes.cpp b/FluidNC/src/Machine/Axes.cpp index 4e94efd7b..06f349f9a 100644 --- a/FluidNC/src/Machine/Axes.cpp +++ b/FluidNC/src/Machine/Axes.cpp @@ -13,10 +13,11 @@ EnumItem axisType[] = { { 0, "X" }, { 1, "Y" }, { 2, "Z" }, { 3, "A" }, { 4, "B" namespace Machine { MotorMask Axes::posLimitMask = 0; MotorMask Axes::negLimitMask = 0; - MotorMask Axes::homingMask = 0; MotorMask Axes::limitMask = 0; MotorMask Axes::motorMask = 0; + AxisMask Axes::homingMask = 0; + Axes::Axes() : _axis() { for (int i = 0; i < MAX_N_AXIS; ++i) { _axis[i] = nullptr; diff --git a/FluidNC/src/Machine/Axes.h b/FluidNC/src/Machine/Axes.h index bb2e65588..90bff23de 100644 --- a/FluidNC/src/Machine/Axes.h +++ b/FluidNC/src/Machine/Axes.h @@ -24,10 +24,11 @@ namespace Machine { // Bitmasks to collect information about axes that have limits and homing static MotorMask posLimitMask; static MotorMask negLimitMask; - static MotorMask homingMask; static MotorMask limitMask; static MotorMask motorMask; + static AxisMask homingMask; + Pin _sharedStepperDisable; Pin _sharedStepperReset; @@ -72,7 +73,8 @@ namespace Machine { void config_motors(); std::string maskToNames(AxisMask mask); - bool namesToMask(const char* names, AxisMask& mask); + + bool namesToMask(const char* names, AxisMask& mask); std::string motorMaskToNames(MotorMask mask); diff --git a/FluidNC/src/Machine/Axis.cpp b/FluidNC/src/Machine/Axis.cpp index 3d0be46ce..8c78cd6bb 100644 --- a/FluidNC/src/Machine/Axis.cpp +++ b/FluidNC/src/Machine/Axis.cpp @@ -45,7 +45,7 @@ namespace Machine { m->init(); } } - if (_homing) { + if (_homing->_cycle) { _homing->init(); set_bitnum(Axes::homingMask, _axis); } diff --git a/FluidNC/src/Machine/Homing.cpp b/FluidNC/src/Machine/Homing.cpp index ef780b54b..644b19371 100644 --- a/FluidNC/src/Machine/Homing.cpp +++ b/FluidNC/src/Machine/Homing.cpp @@ -40,6 +40,16 @@ namespace Machine { std::queue Homing::_remainingCycles; uint32_t Homing::_settling_ms; + AxisMask Homing::_unhomed_axes; // Bitmap of axes whose position is unknown + + bool Homing::axis_is_homed(size_t axis) { return bitnum_is_false(_unhomed_axes, axis); } + void Homing::set_axis_homed(size_t axis) { clear_bitnum(_unhomed_axes, axis); } + void Homing::set_axis_unhomed(size_t axis) { set_bitnum(_unhomed_axes, axis); } + void Homing::set_all_axes_unhomed() { _unhomed_axes = Machine::Axes::homingMask; } + void Homing::set_all_axes_homed() { _unhomed_axes = 0; } + + AxisMask Homing::unhomed_axes() { return _unhomed_axes; } + const char* Homing::_phaseNames[] = { "None", "PrePulloff", "FastApproach", "Pulloff0", "SlowApproach", "Pulloff1", "Pulloff2", "CycleDone", }; @@ -319,9 +329,9 @@ namespace Machine { config->_stepping->endLowLatency(); - if (!sys.abort) { // Execute startup scripts after successful homing. - sys.state = State::Idle; // Set to IDLE when complete. - Stepper::go_idle(); // Set steppers to the settings idle state before returning. + if (!sys.abort) { + sys.state = unhomed_axes() ? State::Alarm : State::Idle; + Stepper::go_idle(); // Set steppers to the settings idle state before returning. } } @@ -389,6 +399,7 @@ namespace Machine { // Replace coordinates homed axes with the homing values. for (size_t axis = 0; axis < n_axis; axis++) { if (bitnum_is_true(_cycleAxes, axis)) { + set_axis_homed(axis); mpos[axis] = axes->_axis[axis]->_homing->_mpos; } } diff --git a/FluidNC/src/Machine/Homing.h b/FluidNC/src/Machine/Homing.h index d1a780c22..682e9678a 100644 --- a/FluidNC/src/Machine/Homing.h +++ b/FluidNC/src/Machine/Homing.h @@ -11,6 +11,8 @@ namespace Machine { class Homing : public Configuration::Configurable { + static AxisMask _unhomed_axes; + public: static enum Phase { None = 0, @@ -23,6 +25,14 @@ namespace Machine { CycleDone = 7, } _phase; + static AxisMask unhomed_axes(); + + static void set_axis_homed(size_t axis); + static void set_axis_unhomed(size_t axis); + static bool axis_is_homed(size_t axis); + static void set_all_axes_homed(); + static void set_all_axes_unhomed(); + Homing() = default; static const int AllCycles = 0; // Must be zero. @@ -44,7 +54,7 @@ namespace Machine { // The homing cycles are 1,2,3 etc. 0 means not homed as part of home-all, // but you can still home it manually with e.g. $HA - int _cycle = -1; // what auto-homing cycle does this axis home on? + int _cycle = 0; // what auto-homing cycle does this axis home on? bool _allow_single_axis = true; // Allow use of $H command on this axis bool _positiveDirection = true; float _mpos = 0.0f; // After homing this will be the mpos of the switch location diff --git a/FluidNC/src/Main.cpp b/FluidNC/src/Main.cpp index 0b7600c52..a00a18cc0 100644 --- a/FluidNC/src/Main.cpp +++ b/FluidNC/src/Main.cpp @@ -127,9 +127,11 @@ void setup() { // NOTE: The startup script will run after successful completion of the homing cycle, but // not after disabling the alarm locks. Prevents motion startup blocks from crashing into // things uncontrollably. Very bad. + Homing::set_all_axes_homed(); if (config->_start->_mustHome && Machine::Axes::homingMask) { + Homing::set_all_axes_unhomed(); // If there is an axis with homing configured, enter Alarm state on startup - sys.state = State::Alarm; + send_alarm(ExecAlarm::Unhomed); } for (auto s : config->_spindles) { s->init(); diff --git a/FluidNC/src/MotionControl.cpp b/FluidNC/src/MotionControl.cpp index fcb054391..b78999df2 100644 --- a/FluidNC/src/MotionControl.cpp +++ b/FluidNC/src/MotionControl.cpp @@ -109,165 +109,11 @@ static bool mc_linear_no_check(float* target, plan_line_data_t* pl_data, float* } bool mc_linear(float* target, plan_line_data_t* pl_data, float* position) { if (!pl_data->is_jog) { // soft limits for jogs have already been dealt with - limits_soft_check(target); - } - return mc_linear_no_check(target, pl_data, position); -} - -// Check that the arc does not exceed the soft limits using a fast -// algorithm that requires no transcendental functions. -// caxes[] depends on the plane selection via G17, G18, and G19. caxes[0] is the first -// circle plane axis, caxes[1] is the second circle plane axis, and caxes[2] is the -// orthogonal plane. So for G17 mode, caxes[] is { 0, 1, 2} for { X, Y, Z}. G18 is {2, 0, 1} i.e. {Z, X, Y}, and G19 is {1, 2, 0} i.e. {Y, Z, X} -void check_arc_limits(float* target, float* position, float center[3], float radius, size_t caxes[3], bool is_clockwise_arc) { - auto axes = config->_axes; - - // Handle the orthognal axis first to get it out of the way. - size_t the_axis = caxes[2]; - if (axes->_axis[the_axis]->_softLimits) { - float amin = std::min(position[the_axis], target[the_axis]); - if (amin < limitsMinPosition(the_axis)) { - limit_error(the_axis, amin); - return; - } - float amax = std::max(position[the_axis], target[the_axis]); - if (amax > limitsMaxPosition(the_axis)) { - limit_error(the_axis, amax); - return; - } - } - - bool limited[2] = { axes->_axis[caxes[0]]->_softLimits, axes->_axis[caxes[1]]->_softLimits }; - - // If neither axis of the circular plane has limits enabled, skip the computation - if (!(limited[0] || limited[1])) { - return; - } - - // The origin for this calculation's coordinate system is at the center of the arc. - // The 0 and 1 entries are for the circle plane - // and the 2 entry is the orthogonal (linear) direction - - float s[2], e[2]; // Start and end of arc in the circle plane, relative to center - - // Depending on the arc direction, set the arc start and end points relative - // to the arc center. Afterwards, end is always counterclockwise relative to - // start, thus simplifying the following decision tree. - if (is_clockwise_arc) { - s[0] = target[caxes[0]] - center[0]; - s[1] = target[caxes[1]] - center[1]; - e[0] = position[caxes[0]] - center[0]; - e[1] = position[caxes[1]] - center[1]; - } else { - s[0] = position[caxes[0]] - center[0]; - s[1] = position[caxes[1]] - center[1]; - e[0] = target[caxes[0]] - center[0]; - e[1] = target[caxes[1]] - center[1]; - } - - // Axis crossings - plus and minus caxes[0] and caxes[1] - bool p[2] = { false, false }; - bool m[2] = { false, false }; - - // The following decision tree determines whether the arc crosses - // the horizontal and vertical axes of the circular plane in the - // positive and negative half planes. There are ways to express - // it in fewer lines of code by converting to alternate - // representations like angles, but this way is computationally - // efficient since it avoids any use of transcendental functions. - // Every path through this decision tree is either 4 or 5 simple - // comparisons. - if (e[1] >= 0) { // End in upper half plane - if (e[0] > 0) { // End in quadrant 0 - X+ Y+ - if (s[1] >= 0) { // Start in upper half plane - if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ - if (s[0] <= e[0]) { // wraparound - p[0] = p[1] = m[0] = m[1] = true; - } - } else { // Start in quadrant 1 - X- Y+ - m[0] = m[1] = p[0] = true; - } - } else { // Start in lower half plane - if (s[0] > 0) { // Start in quadrant 3 - X+ Y- - p[0] = true; - } else { // Start in quadrant 2 - X- Y- - m[1] = p[0] = true; - } - } - } else { // End in quadrant 1 - X- Y+ - if (s[1] >= 0) { // Start in upper half plane - if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ - p[1] = true; - } else { // Start in quadrant 1 - X- Y+ - if (s[0] <= e[0]) { // wraparound - p[0] = p[1] = m[0] = m[1] = true; - } - } - } else { // Start in lower half plane - if (s[0] > 0) { // Start in quadrant 3 - X+ Y- - p[0] = p[1] = true; - } else { // Start in quadrant 2 - X- Y- - m[1] = p[0] = p[1] = true; - } - } - } - } else { // e[1] < 0 - end in lower half plane - if (e[0] > 0) { // End in quadrant 3 - X+ Y+ - if (s[1] >= 0) { // Start in upper half plane - if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ - p[1] = m[0] = m[1] = true; - } else { // Start in quadrant 1 - X- Y+ - m[0] = m[1] = true; - } - } else { // Start in lower half plane - if (s[0] > 0) { // Start in quadrant 3 - X+ Y- - if (s[0] >= e[0]) { // wraparound - p[0] = p[1] = m[0] = m[1] = true; - } - } else { // Start in quadrant 2 - X- Y- - m[1] = true; - } - } - } else { // End in quadrant 2 - X- Y+ - if (s[1] >= 0) { // Start in upper half plane - if (s[0] > 0) { // Start in quadrant 0 - X+ Y+ - p[1] = m[0] = true; - } else { // Start in quadrant 1 - X- Y+ - m[0] = true; - } - } else { // Start in lower half plane - if (s[0] > 0) { // Start in quadrant 3 - X+ Y- - p[0] = p[1] = m[0] = true; - } else { // Start in quadrant 2 - X- Y- - if (s[0] >= e[0]) { // wraparound - p[0] = p[1] = m[0] = m[1] = true; - } - } - } - } - } - - // Now check limits based on arc endpoints and axis crossings - for (size_t a = 0; a < 2; ++a) { - the_axis = caxes[a]; - if (limited[a]) { - // If we crossed the axis in the positive half plane, the - // maximum extent along that axis is at center + radius. - // Otherwise it is the maximum coordinate of the start and - // end positions. Similarly for the negative half plane - // and the minimum extent. - float amin = m[a] ? center[a] - radius : std::min(target[the_axis], position[the_axis]); - if (amin < limitsMinPosition(the_axis)) { - limit_error(the_axis, amin); - return; - } - float amax = p[a] ? center[a] + radius : std::max(target[the_axis], position[the_axis]); - if (amax > limitsMaxPosition(the_axis)) { - limit_error(the_axis, amax); - return; - } + if (config->_kinematics->invalid_line(target)) { + return false; } } + return mc_linear_no_check(target, pl_data, position); } // Execute an arc in offset mode format. position == current xyz, target == target xyz, @@ -291,7 +137,9 @@ void mc_arc(float* target, // The first two axes are the circle plane and the third is the orthogonal plane size_t caxes[3] = { axis_0, axis_1, axis_linear }; - check_arc_limits(target, position, center, radius, caxes, is_clockwise_arc); + if (config->_kinematics->invalid_arc(target, position, center, radius, caxes, is_clockwise_arc)) { + return; + } // Radius vector from center to current location float radii[2] = { -offset[axis_0], -offset[axis_1] }; diff --git a/FluidNC/src/Motors/StandardStepper.cpp b/FluidNC/src/Motors/StandardStepper.cpp index fa519ffcc..20b3aec2b 100644 --- a/FluidNC/src/Motors/StandardStepper.cpp +++ b/FluidNC/src/Motors/StandardStepper.cpp @@ -84,7 +84,9 @@ namespace MotorDrivers { _step_pin.setAttr(Pin::Attr::Output); } - _disable_pin.setAttr(Pin::Attr::Output); + if (_disable_pin.defined()) { + _disable_pin.setAttr(Pin::Attr::Output); + } } void StandardStepper::config_message() { diff --git a/FluidNC/src/Planner.cpp b/FluidNC/src/Planner.cpp index e42e2834f..c0b717448 100644 --- a/FluidNC/src/Planner.cpp +++ b/FluidNC/src/Planner.cpp @@ -306,8 +306,16 @@ bool plan_buffer_line(float* target, plan_line_data_t* pl_data) { int32_t target_steps[MAX_N_AXIS], position_steps[MAX_N_AXIS]; float unit_vec[MAX_N_AXIS], delta_mm; // Copy position data based on type of motion being planned. - copyAxes(position_steps, block->motion.systemMotion ? get_motor_steps() : pl.position); - + if (block->motion.systemMotion) { + copyAxes(position_steps, get_motor_steps()); + } else { + if (!block->is_jog && Homing::unhomed_axes()) { + log_info("Unhomed axes: " << config->_axes->maskToNames(Homing::unhomed_axes())); + send_alarm(ExecAlarm::Unhomed); + return false; + } + copyAxes(position_steps, pl.position); + } auto n_axis = config->_axes->_numberAxis; for (size_t idx = 0; idx < n_axis; idx++) { // Calculate target position in absolute steps, number of steps for each axis, and determine max step events. diff --git a/FluidNC/src/Planner.h b/FluidNC/src/Planner.h index 684c3104f..fe4d21e1f 100644 --- a/FluidNC/src/Planner.h +++ b/FluidNC/src/Planner.h @@ -12,6 +12,7 @@ #include "Config.h" // MAX_N_AXIS #include "SpindleDatatypes.h" // SpindleState #include "GCode.h" // CoolantState +#include "Types.h" // AxisMask #include diff --git a/FluidNC/src/ProcessSettings.cpp b/FluidNC/src/ProcessSettings.cpp index d73155f64..5e2991af1 100644 --- a/FluidNC/src/ProcessSettings.cpp +++ b/FluidNC/src/ProcessSettings.cpp @@ -286,6 +286,7 @@ static Error disable_alarm_lock(const char* value, WebUI::AuthenticationLevel au if (err != Error::Ok) { return err; } + Homing::set_all_axes_homed(); report_feedback_message(Message::AlarmUnlock); sys.state = State::Idle; diff --git a/FluidNC/src/Protocol.cpp b/FluidNC/src/Protocol.cpp index 587c818a5..18c87ef03 100644 --- a/FluidNC/src/Protocol.cpp +++ b/FluidNC/src/Protocol.cpp @@ -36,6 +36,7 @@ std::map AlarmNames = { { ExecAlarm::ControlPin, "Control Pin Initially On" }, { ExecAlarm::HomingAmbiguousSwitch, "Ambiguous Switch" }, { ExecAlarm::HardStop, "Hard Stop" }, + { ExecAlarm::Unhomed, "Unhomed" }, }; const char* alarmString(ExecAlarm alarmNumber) { diff --git a/FluidNC/src/Protocol.h b/FluidNC/src/Protocol.h index 2b44361dd..da11aa898 100644 --- a/FluidNC/src/Protocol.h +++ b/FluidNC/src/Protocol.h @@ -64,6 +64,7 @@ enum class ExecAlarm : uint8_t { ControlPin = 11, HomingAmbiguousSwitch = 12, HardStop = 13, + Unhomed = 14, }; extern volatile ExecAlarm lastAlarm; @@ -97,6 +98,7 @@ extern NoArgEvent motionCancelEvent; extern NoArgEvent sleepEvent; extern NoArgEvent rtResetEvent; extern NoArgEvent debugEvent; +extern NoArgEvent unhomedEvent; // extern NoArgEvent statusReportEvent;