From c65c1d90d353825c24d64e11a85b16d0cd55cdf7 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Thu, 3 Oct 2024 10:36:35 -1000 Subject: [PATCH 01/30] Make a lot of stuff static --- FluidNC/src/GCode.cpp | 4 +- FluidNC/src/Kinematics/Cartesian.cpp | 6 +-- FluidNC/src/Kinematics/CoreXY.cpp | 8 ++-- FluidNC/src/Kinematics/ParallelDelta.cpp | 4 +- FluidNC/src/Kinematics/WallPlotter.cpp | 6 +-- FluidNC/src/Limits.cpp | 10 ++--- FluidNC/src/Machine/Axes.cpp | 25 ++++++++----- FluidNC/src/Machine/Axes.h | 38 +++++++++---------- FluidNC/src/Machine/Homing.cpp | 26 ++++++------- FluidNC/src/Machine/LimitPin.cpp | 10 +++-- FluidNC/src/Machine/MachineConfig.h | 2 +- FluidNC/src/Main.cpp | 4 +- FluidNC/src/MotionControl.cpp | 12 +++--- FluidNC/src/Motors/Dynamixel2.cpp | 4 +- FluidNC/src/Motors/MotorDriver.cpp | 6 ++- FluidNC/src/Motors/RcServo.cpp | 2 +- FluidNC/src/Motors/TrinamicBase.cpp | 4 +- FluidNC/src/Motors/UnipolarMotor.cpp | 10 +++-- FluidNC/src/NutsBolts.cpp | 10 ++--- FluidNC/src/OLED.cpp | 2 +- FluidNC/src/Pin.h | 2 +- FluidNC/src/Pins/ChannelPinDetail.cpp | 14 +++++-- FluidNC/src/Pins/DebugPinDetail.cpp | 4 +- FluidNC/src/Pins/ErrorPinDetail.cpp | 20 +++++++--- FluidNC/src/Planner.cpp | 4 +- FluidNC/src/ProcessSettings.cpp | 14 +++---- FluidNC/src/Protocol.cpp | 18 ++++----- FluidNC/src/Report.cpp | 8 ++-- FluidNC/src/Stepper.cpp | 34 ++++++----------- FluidNC/src/Stepping.cpp | 8 ++++ FluidNC/src/Stepping.h | 47 +++++++++++++----------- FluidNC/src/System.cpp | 6 +-- FluidNC/src/WebUI/WifiConfig.cpp | 8 ++-- 33 files changed, 205 insertions(+), 175 deletions(-) diff --git a/FluidNC/src/GCode.cpp b/FluidNC/src/GCode.cpp index ab64bc093..2fca261d6 100644 --- a/FluidNC/src/GCode.cpp +++ b/FluidNC/src/GCode.cpp @@ -268,7 +268,7 @@ Error gc_execute_line(char* line) { bool nonmodalG38 = false; // Used for G38.6-9 bool isWaitOnInputDigital = false; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; float coord_data[MAX_N_AXIS]; // Used by WCO-related commands uint8_t pValue; // Integer value of P word @@ -1602,7 +1602,7 @@ Error gc_execute_line(char* line) { // NOTE: Pass zero spindle speed for all restricted laser motions. if (!disableLaser) { pl_data->spindle_speed = gc_state.spindle_speed; // Record data for planner use. - } // else { pl_data->spindle_speed = 0.0; } // Initialized as zero already. + } // else { pl_data->spindle_speed = 0.0; } // Initialized as zero already. // [5. Select tool ]: NOT SUPPORTED. Only tracks tool value. // gc_state.tool = gc_block.values.t; // [M6. Change tool ]: diff --git a/FluidNC/src/Kinematics/Cartesian.cpp b/FluidNC/src/Kinematics/Cartesian.cpp index 00b1318bd..b318f5dc5 100644 --- a/FluidNC/src/Kinematics/Cartesian.cpp +++ b/FluidNC/src/Kinematics/Cartesian.cpp @@ -12,7 +12,7 @@ namespace Kinematics { // Initialize the machine position void Cartesian::init_position() { - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = 0; axis < n_axis; axis++) { set_motor_steps(axis, 0); // Set to zeros } @@ -178,7 +178,7 @@ namespace Kinematics { void Cartesian::constrain_jog(float* target, plan_line_data_t* pl_data, float* position) { auto axes = config->_axes; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; float* current_position = get_mpos(); MotorMask lim_pin_state = limits_get_state(); @@ -237,7 +237,7 @@ namespace Kinematics { bool Cartesian::invalid_line(float* cartesian) { auto axes = config->_axes; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (int axis = 0; axis < n_axis; axis++) { float coordinate = cartesian[axis]; diff --git a/FluidNC/src/Kinematics/CoreXY.cpp b/FluidNC/src/Kinematics/CoreXY.cpp index 76815c75d..7a531b9f3 100644 --- a/FluidNC/src/Kinematics/CoreXY.cpp +++ b/FluidNC/src/Kinematics/CoreXY.cpp @@ -33,8 +33,8 @@ namespace Kinematics { log_info("Kinematic system: " << name()); // A limit switch on either axis stops both motors - config->_axes->_axis[X_AXIS]->_motors[0]->limitOtherAxis(Y_AXIS); - config->_axes->_axis[Y_AXIS]->_motors[0]->limitOtherAxis(X_AXIS); + Axes::_axis[X_AXIS]->_motors[0]->limitOtherAxis(Y_AXIS); + Axes::_axis[Y_AXIS]->_motors[0]->limitOtherAxis(X_AXIS); } bool CoreXY::canHome(AxisMask axisMask) { @@ -93,7 +93,7 @@ namespace Kinematics { bool CoreXY::cartesian_to_motors(float* target, plan_line_data_t* pl_data, float* position) { // log_debug("cartesian_to_motors position (" << position[X_AXIS] << "," << position[Y_AXIS] << ") target (" << target[X_AXIS] << "," << target[Y_AXIS] << ")"); - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; float motors[n_axis]; transform_cartesian_to_motors(motors, target); @@ -136,7 +136,7 @@ namespace Kinematics { motors[X_AXIS] = (_x_scaler * cartesian[X_AXIS]) + cartesian[Y_AXIS]; motors[Y_AXIS] = (_x_scaler * cartesian[X_AXIS]) - cartesian[Y_AXIS]; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = Z_AXIS; axis < n_axis; axis++) { motors[axis] = cartesian[axis]; } diff --git a/FluidNC/src/Kinematics/ParallelDelta.cpp b/FluidNC/src/Kinematics/ParallelDelta.cpp index cd4be2822..e5e34d81b 100644 --- a/FluidNC/src/Kinematics/ParallelDelta.cpp +++ b/FluidNC/src/Kinematics/ParallelDelta.cpp @@ -85,7 +85,7 @@ namespace Kinematics { log_info("Kinematic system:" << name() << " soft_limits:" << _softLimits); auto axes = config->_axes; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; // warn about axis soft limits for (int axis = 0; axis < n_axis; axis++) { @@ -293,7 +293,7 @@ namespace Kinematics { auto axes = config->_axes; auto n_axis = axes->_numberAxis; - config->_axes->set_disable(false); + Axes::set_disable(false); // TODO deal with non kinematic axes above Z for (int axis = 0; axis < 3; axis++) { diff --git a/FluidNC/src/Kinematics/WallPlotter.cpp b/FluidNC/src/Kinematics/WallPlotter.cpp index 10e5a09f3..dc66e5f86 100644 --- a/FluidNC/src/Kinematics/WallPlotter.cpp +++ b/FluidNC/src/Kinematics/WallPlotter.cpp @@ -26,7 +26,7 @@ namespace Kinematics { xy_to_lengths(0, 0, zero_left, zero_right); last_motor_segment_end[0] = zero_left; last_motor_segment_end[1] = zero_right; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = Z_AXIS; axis < n_axis; axis++) { last_motor_segment_end[axis] = 0.0; } @@ -36,7 +36,7 @@ namespace Kinematics { // Initialize the machine position void WallPlotter::init_position() { - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = 0; axis < n_axis; axis++) { set_motor_steps(axis, 0); // Set to zeros } @@ -66,7 +66,7 @@ namespace Kinematics { float dx, dy, dz; // segment distances in each cartesian axis uint32_t segment_count; // number of segments the move will be broken in to. - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; float total_cartesian_distance = vector_distance(position, target, n_axis); if (total_cartesian_distance == 0) { diff --git a/FluidNC/src/Limits.cpp b/FluidNC/src/Limits.cpp index e9594fd42..3c363de0c 100644 --- a/FluidNC/src/Limits.cpp +++ b/FluidNC/src/Limits.cpp @@ -47,16 +47,16 @@ MotorMask limits_get_state() { bool limits_startup_check() { // return true if there is a hard limit error. MotorMask lim_pin_state = limits_get_state(); if (lim_pin_state) { - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = 0; axis < n_axis; axis++) { for (size_t motor = 0; motor < 2; motor++) { if (bitnum_is_true(lim_pin_state, Machine::Axes::motor_bit(axis, motor))) { - log_warn("Active limit switch on " << config->_axes->axisName(axis) << " axis motor " << motor); + log_warn("Active limit switch on " << Axes::axisName(axis) << " axis motor " << motor); } } } } - return (config->_start->_checkLimits && (config->_axes->hardLimitMask() & lim_pin_state)); + return (config->_start->_checkLimits && (Axes::hardLimitMask() & lim_pin_state)); } // Called only from Kinematics canHome() methods, hence from states allowing homing @@ -99,7 +99,7 @@ void limit_error() { } float limitsMaxPosition(size_t axis) { - auto axisConfig = config->_axes->_axis[axis]; + auto axisConfig = Axes::_axis[axis]; auto homing = axisConfig->_homing; auto mpos = homing ? homing->_mpos : 0; auto maxtravel = axisConfig->_maxTravel; @@ -108,7 +108,7 @@ float limitsMaxPosition(size_t axis) { } float limitsMinPosition(size_t axis) { - auto axisConfig = config->_axes->_axis[axis]; + auto axisConfig = Axes::_axis[axis]; auto homing = axisConfig->_homing; auto mpos = homing ? homing->_mpos : 0; auto maxtravel = axisConfig->_maxTravel; diff --git a/FluidNC/src/Machine/Axes.cpp b/FluidNC/src/Machine/Axes.cpp index b16068890..0fff03c1d 100644 --- a/FluidNC/src/Machine/Axes.cpp +++ b/FluidNC/src/Machine/Axes.cpp @@ -20,14 +20,19 @@ namespace Machine { bool Axes::disabled = false; - Axes::Axes() : _axis() { - for (int i = 0; i < MAX_N_AXIS; ++i) { - _axis[i] = nullptr; - } - } + Pin Axes::_sharedStepperDisable; + Pin Axes::_sharedStepperReset; + + uint32_t Axes::_homing_runs = 2; // Number of Approach/Pulloff cycles + + int Axes::_numberAxis = 0; + + Axis* Axes::_axis[MAX_N_AXIS] = { nullptr }; + + Axes::Axes() {} void Axes::init() { - log_info("Axis count " << config->_axes->_numberAxis); + log_info("Axis count " << Axes::_numberAxis); if (_sharedStepperDisable.defined()) { _sharedStepperDisable.setAttr(Pin::Attr::Output); @@ -72,8 +77,8 @@ namespace Machine { if (!disable && disabled) { disabled = false; - if (config->_stepping->_disableDelayUsecs) { // wait for the enable delay - delay_us(config->_stepping->_disableDelayUsecs); + if (Stepping::_disableDelayUsecs) { // wait for the enable delay + delay_us(Stepping::_disableDelayUsecs); } } } @@ -167,7 +172,7 @@ namespace Machine { // Some small helpers to find the axis index and axis motor index for a given motor. This // is helpful for some motors that need this info, as well as debug information. - size_t Axes::findAxisIndex(const MotorDrivers::MotorDriver* const driver) const { + size_t Axes::findAxisIndex(const MotorDrivers::MotorDriver* const driver) { for (int i = 0; i < _numberAxis; ++i) { for (int j = 0; j < Axis::MAX_MOTORS_PER_AXIS; ++j) { if (_axis[i] != nullptr && _axis[i]->hasMotor(driver)) { @@ -180,7 +185,7 @@ namespace Machine { return SIZE_MAX; } - size_t Axes::findAxisMotor(const MotorDrivers::MotorDriver* const driver) const { + size_t Axes::findAxisMotor(const MotorDrivers::MotorDriver* const driver) { for (int i = 0; i < _numberAxis; ++i) { if (_axis[i] != nullptr && _axis[i]->hasMotor(driver)) { for (int j = 0; j < Axis::MAX_MOTORS_PER_AXIS; ++j) { diff --git a/FluidNC/src/Machine/Axes.h b/FluidNC/src/Machine/Axes.h index 91201f45f..54003f835 100644 --- a/FluidNC/src/Machine/Axes.h +++ b/FluidNC/src/Machine/Axes.h @@ -31,26 +31,26 @@ namespace Machine { static bool disabled; - Pin _sharedStepperDisable; - Pin _sharedStepperReset; + static Pin _sharedStepperDisable; + static Pin _sharedStepperReset; - uint32_t _homing_runs = 2; // Number of Approach/Pulloff cycles + static uint32_t _homing_runs; // Number of Approach/Pulloff cycles - inline char axisName(int index) { return index < MAX_N_AXIS ? _names[index] : '?'; } // returns axis letter + static inline char axisName(int index) { return index < MAX_N_AXIS ? _names[index] : '?'; } // returns axis letter static inline size_t motor_bit(size_t axis, size_t motor) { return motor ? axis + 16 : axis; } static inline AxisMask motors_to_axes(MotorMask motors) { return (motors & 0xffff) | (motors >> 16); } static inline MotorMask axes_to_motors(AxisMask axes) { return axes | (axes << 16); } - int _numberAxis = 0; - Axis* _axis[MAX_N_AXIS]; + static int _numberAxis; + static Axis* _axis[MAX_N_AXIS]; // Some small helpers to find the axis index and axis motor number for a given motor. This // is helpful for some motors that need this info, as well as debug information. - size_t findAxisIndex(const MotorDrivers::MotorDriver* const motor) const; - size_t findAxisMotor(const MotorDrivers::MotorDriver* const motor) const; + static size_t findAxisIndex(const MotorDrivers::MotorDriver* const motor); + static size_t findAxisMotor(const MotorDrivers::MotorDriver* const motor); - MotorMask hardLimitMask(); + static MotorMask hardLimitMask(); inline bool hasHardLimits() const { for (int axis = 0; axis < _numberAxis; ++axis) { @@ -66,23 +66,23 @@ namespace Machine { return false; } - void init(); + static void init(); // These are used during homing cycles. // The return value is a bitmask of axes that can home - MotorMask set_homing_mode(AxisMask homing_mask, bool isHoming); + static MotorMask set_homing_mode(AxisMask homing_mask, bool isHoming); - void set_disable(int axis, bool disable); - void set_disable(bool disable); - void step(uint8_t step_mask, uint8_t dir_mask); - void unstep(); - void config_motors(); + static void set_disable(int axis, bool disable); + static void set_disable(bool disable); + static void step(uint8_t step_mask, uint8_t dir_mask); + static void unstep(); + static void config_motors(); - std::string maskToNames(AxisMask mask); + static std::string maskToNames(AxisMask mask); - bool namesToMask(const char* names, AxisMask& mask); + static bool namesToMask(const char* names, AxisMask& mask); - std::string motorMaskToNames(MotorMask mask); + static std::string motorMaskToNames(MotorMask mask); // Configuration helpers: void group(Configuration::HandlerBase& handler) override; diff --git a/FluidNC/src/Machine/Homing.cpp b/FluidNC/src/Machine/Homing.cpp index 8e54abd21..470c200fe 100644 --- a/FluidNC/src/Machine/Homing.cpp +++ b/FluidNC/src/Machine/Homing.cpp @@ -70,7 +70,7 @@ namespace Machine { void Homing::startMove(AxisMask axisMask, MotorMask motors, Phase phase, uint32_t& settle_ms) { float rate; - float target[config->_axes->_numberAxis]; + float target[Axes::_numberAxis]; axisVector(_phaseAxes, _phaseMotors, _phase, target, rate, _settling_ms); plan_line_data_t plan_data = {}; @@ -318,7 +318,7 @@ namespace Machine { return; } - log_debug("Homing limited" << config->_axes->motorMaskToNames(limited)); + log_debug("Homing limited" << Axes::motorMaskToNames(limited)); bool stop = config->_kinematics->limitReached(_phaseAxes, _phaseMotors, limited); @@ -329,7 +329,7 @@ namespace Machine { Stepper::reset(); // Stop moving if (_phaseAxes) { - log_debug("Homing replan with " << config->_axes->maskToNames(_phaseAxes)); + log_debug("Homing replan with " << Axes::maskToNames(_phaseAxes)); config->_kinematics->releaseMotors(_phaseAxes, _phaseMotors); @@ -380,21 +380,21 @@ namespace Machine { _cycleAxes = _remainingCycles.front(); _remainingCycles.pop(); - log_debug("Homing Cycle " << config->_axes->maskToNames(_cycleAxes)); + log_debug("Homing Cycle " << Axes::maskToNames(_cycleAxes)); _cycleAxes &= Machine::Axes::homingMask; - _cycleMotors = config->_axes->set_homing_mode(_cycleAxes, true); + _cycleMotors = Axes::set_homing_mode(_cycleAxes, true); _phase = Phase::PrePulloff; - _runs = config->_axes->_homing_runs; + _runs = Axes::_homing_runs; runPhase(); } void Homing::fail(ExecAlarm alarm) { Stepper::reset(); // Stop moving send_alarm(alarm); - config->_axes->set_homing_mode(_cycleAxes, false); // tell motors homing is done...failed - config->_axes->set_disable(config->_stepping->_idleMsecs != 255); + Axes::set_homing_mode(_cycleAxes, false); // tell motors homing is done...failed + Axes::set_disable(Stepping::_idleMsecs != 255); } bool Homing::needsPulloff2(MotorMask motors) { @@ -453,7 +453,7 @@ namespace Machine { #if 0 static std::string axisNames(AxisMask axisMask) { std::string retval = ""; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = 0; axis < n_axis; axis++) { if (bitnum_is_true(axisMask, axis)) { retval += Machine::Axes::_names[axis]; @@ -479,9 +479,9 @@ namespace Machine { } // Find any cycles that set the m_pos without motion - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (int axis = X_AXIS; axis < n_axis; axis++) { - auto homing = config->_axes->_axis[axis]->_homing; + auto homing = Axes::_axis[axis]->_homing; if (homing && homing->_cycle == set_mpos_only) { if (axisMask == 0 || axisMask & 1 << axis) { float* mpos = get_mpos(); @@ -529,9 +529,9 @@ namespace Machine { AxisMask Homing::axis_mask_from_cycle(int cycle) { AxisMask axisMask = 0; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (int axis = 0; axis < n_axis; axis++) { - auto axisConfig = config->_axes->_axis[axis]; + auto axisConfig = Axes::_axis[axis]; auto homing = axisConfig->_homing; if (homing && homing->_cycle == cycle) { set_bitnum(axisMask, axis); diff --git a/FluidNC/src/Machine/LimitPin.cpp b/FluidNC/src/Machine/LimitPin.cpp index dd5eeaf9d..f2b4b96e7 100644 --- a/FluidNC/src/Machine/LimitPin.cpp +++ b/FluidNC/src/Machine/LimitPin.cpp @@ -37,7 +37,7 @@ namespace Machine { // Set a bitmap with bits to represent the axis and which motors are affected // The bitmap looks like CBAZYX..cbazyx where motor0 motors are in the lower bits _bitmask = 1 << Axes::motor_bit(axis, motor); - _legend = config->_axes->motorMaskToNames(_bitmask); + _legend = Axes::motorMaskToNames(_bitmask); _legend += " "; _legend += sDir; _legend += " Limit"; @@ -87,7 +87,11 @@ namespace Machine { // Make this switch act like an axis level switch. Both motors will report the same // This should be called from a higher level object, that has the logic to figure out // if this belongs to a dual motor, single switch axis - void LimitPin::makeDualMask() { _bitmask = Axes::axes_to_motors(Axes::motors_to_axes(_bitmask)); } + void LimitPin::makeDualMask() { + _bitmask = Axes::axes_to_motors(Axes::motors_to_axes(_bitmask)); + } - void LimitPin::setExtraMotorLimit(int axis, int motorNum) { _pExtraLimited = &config->_axes->_axis[axis]->_motors[motorNum]->_limited; } + void LimitPin::setExtraMotorLimit(int axis, int motorNum) { + _pExtraLimited = &config->_axes->_axis[axis]->_motors[motorNum]->_limited; + } } diff --git a/FluidNC/src/Machine/MachineConfig.h b/FluidNC/src/Machine/MachineConfig.h index 278a9150e..9aa847799 100644 --- a/FluidNC/src/Machine/MachineConfig.h +++ b/FluidNC/src/Machine/MachineConfig.h @@ -120,7 +120,7 @@ extern Machine::MachineConfig* config; template void copyAxes(T* dest, T* src) { - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = 0; axis < n_axis; axis++) { dest[axis] = src[axis]; } diff --git a/FluidNC/src/Main.cpp b/FluidNC/src/Main.cpp index 3a356840a..b620578d3 100644 --- a/FluidNC/src/Main.cpp +++ b/FluidNC/src/Main.cpp @@ -90,7 +90,7 @@ void setup() { } } - config->_stepping->init(); // Configure stepper interrupt timers + Stepping::init(); // Configure stepper interrupt timers plan_init(); @@ -98,7 +98,7 @@ void setup() { config->_userInputs->init(); - config->_axes->init(); + Axes::init(); config->_control->init(); diff --git a/FluidNC/src/MotionControl.cpp b/FluidNC/src/MotionControl.cpp index a03904892..243b9532f 100644 --- a/FluidNC/src/MotionControl.cpp +++ b/FluidNC/src/MotionControl.cpp @@ -145,7 +145,7 @@ void mc_arc(float* target, float radii[2] = { -offset[axis_0], -offset[axis_1] }; float rt[2] = { target[axis_0] - center[0], target[axis_1] - center[1] }; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; float previous_position[n_axis] = { 0.0 }; for (size_t i = 0; i < n_axis; i++) { @@ -295,7 +295,7 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, bool away, return GCUpdatePos::None; // Return if system reset has been issued. } - config->_stepping->beginLowLatency(); + Stepping::beginLowLatency(); // Initialize probing control variables probe_succeeded = false; // Re-initialize probe history before beginning cycle. @@ -305,7 +305,7 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, bool away, if (config->_probe->tripped()) { send_alarm(ExecAlarm::ProbeFailInitial); protocol_execute_realtime(); - config->_stepping->endLowLatency(); + Stepping::endLowLatency(); return GCUpdatePos::None; // Nothing else to do but bail. } // Setup and queue probing motion. Auto cycle-start should not start the cycle. @@ -317,12 +317,12 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, bool away, do { protocol_execute_realtime(); if (sys.abort) { - config->_stepping->endLowLatency(); + Stepping::endLowLatency(); return GCUpdatePos::None; // Check for system abort } } while (!state_is(State::Idle)); - config->_stepping->endLowLatency(); + Stepping::endLowLatency(); // Probing cycle complete! // Set state variables and error out, if the probe failed and cycle with error is enabled. @@ -355,7 +355,7 @@ GCUpdatePos mc_probe_cycle(float* target, plan_line_data_t* pl_data, bool away, motor_steps_to_mpos(probe_contact, probe_steps); coords[gc_state.modal.coord_select]->get(coord_data); // get a copy of the current coordinate offsets - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (int axis = 0; axis < n_axis; axis++) { // find the axis specified. There should only be one. if (offsetAxis & (1 << axis)) { coord_data[axis] = probe_contact[axis] - offset; diff --git a/FluidNC/src/Motors/Dynamixel2.cpp b/FluidNC/src/Motors/Dynamixel2.cpp index 63a837b0b..33ffb77f1 100644 --- a/FluidNC/src/Motors/Dynamixel2.cpp +++ b/FluidNC/src/Motors/Dynamixel2.cpp @@ -171,7 +171,7 @@ namespace MotorDrivers { return false; } - auto axisConfig = config->_axes->_axis[_axis_index]; + auto axisConfig = Axes::_axis[_axis_index]; auto homing = axisConfig->_homing; auto mpos = homing ? homing->_mpos : 0; set_motor_steps(_axis_index, mpos_to_steps(mpos, _axis_index)); @@ -237,7 +237,7 @@ namespace MotorDrivers { if (data_len == 15) { uint32_t dxl_position = _rx_message[9] | (_rx_message[10] << 8) | (_rx_message[11] << 16) | (_rx_message[12] << 24); - auto axis = config->_axes->_axis[_axis_index]; + auto axis = Axes::_axis[_axis_index]; uint32_t pos_min_steps = mpos_to_steps(limitsMinPosition(_axis_index), _axis_index); uint32_t pos_max_steps = mpos_to_steps(limitsMaxPosition(_axis_index), _axis_index); diff --git a/FluidNC/src/Motors/MotorDriver.cpp b/FluidNC/src/Motors/MotorDriver.cpp index 47b5aaf37..3c7988f2d 100644 --- a/FluidNC/src/Motors/MotorDriver.cpp +++ b/FluidNC/src/Motors/MotorDriver.cpp @@ -27,12 +27,14 @@ namespace MotorDrivers { std::string MotorDriver::axisName() const { - return std::string(1, config->_axes->axisName(axis_index())) + (dual_axis_index() ? "2" : "") + " Axis"; + return std::string(1, Axes::axisName(axis_index())) + (dual_axis_index() ? "2" : "") + " Axis"; } void MotorDriver::debug_message() {} - bool MotorDriver::test() { return true; }; // true = OK + bool MotorDriver::test() { + return true; + }; // true = OK size_t MotorDriver::axis_index() const { Assert(config != nullptr && config->_axes != nullptr, "Expected machine to be configured before this is called."); diff --git a/FluidNC/src/Motors/RcServo.cpp b/FluidNC/src/Motors/RcServo.cpp index 2d99aeb7f..4243ad46e 100644 --- a/FluidNC/src/Motors/RcServo.cpp +++ b/FluidNC/src/Motors/RcServo.cpp @@ -82,7 +82,7 @@ namespace MotorDrivers { return false; if (isHoming) { - auto axisConfig = config->_axes->_axis[_axis_index]; + auto axisConfig = Axes::_axis[_axis_index]; auto homing = axisConfig->_homing; auto mpos = homing ? homing->_mpos : 0; set_motor_steps(_axis_index, mpos_to_steps(mpos, _axis_index)); diff --git a/FluidNC/src/Motors/TrinamicBase.cpp b/FluidNC/src/Motors/TrinamicBase.cpp index 3a2da168e..502ba859c 100644 --- a/FluidNC/src/Motors/TrinamicBase.cpp +++ b/FluidNC/src/Motors/TrinamicBase.cpp @@ -32,11 +32,11 @@ namespace MotorDrivers { // This is used to set the stallguard window from the homing speed. // The percent is the offset on the window uint32_t TrinamicBase::calc_tstep(int percent) { - auto axisConfig = config->_axes->_axis[axis_index()]; + auto axisConfig = Axes::_axis[axis_index()]; auto homing = axisConfig->_homing; auto homingFeedrate = homing ? homing->_feedRate : 200.0; - double tstep = homingFeedrate / 60.0 * config->_axes->_axis[axis_index()]->_stepsPerMm * (256.0 / _microsteps); + double tstep = homingFeedrate / 60.0 * Axes::_axis[axis_index()]->_stepsPerMm * (256.0 / _microsteps); tstep = fclk / tstep * percent / 100.0; return static_cast(tstep); diff --git a/FluidNC/src/Motors/UnipolarMotor.cpp b/FluidNC/src/Motors/UnipolarMotor.cpp index 8b190e6a3..f1b14dde9 100644 --- a/FluidNC/src/Motors/UnipolarMotor.cpp +++ b/FluidNC/src/Motors/UnipolarMotor.cpp @@ -15,8 +15,8 @@ namespace MotorDrivers { } void UnipolarMotor::config_message() { - log_info(" " << name() << " Ph0:" << _pin_phase0.name() << " Ph1:" << _pin_phase1.name() - << " Ph2:" << _pin_phase2.name() << " Ph3:" << _pin_phase3.name()); + log_info(" " << name() << " Ph0:" << _pin_phase0.name() << " Ph1:" << _pin_phase1.name() << " Ph2:" << _pin_phase2.name() + << " Ph3:" << _pin_phase3.name()); } void IRAM_ATTR UnipolarMotor::set_disable(bool disable) { @@ -29,7 +29,9 @@ namespace MotorDrivers { _enabled = !disable; } - void IRAM_ATTR UnipolarMotor::set_direction(bool dir) { _dir = dir; } + void IRAM_ATTR UnipolarMotor::set_direction(bool dir) { + _dir = dir; + } void IRAM_ATTR UnipolarMotor::step() { uint8_t _phase[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // temporary phase values...all start as off @@ -116,7 +118,7 @@ namespace MotorDrivers { _pin_phase1.write(_phase[1]); _pin_phase2.write(_phase[2]); _pin_phase3.write(_phase[3]); - config->_stepping->startPulseTimer(); + Stepping::startPulseTimer(); } // Configuration registration diff --git a/FluidNC/src/NutsBolts.cpp b/FluidNC/src/NutsBolts.cpp index 41207b473..4cbd48e97 100644 --- a/FluidNC/src/NutsBolts.cpp +++ b/FluidNC/src/NutsBolts.cpp @@ -151,7 +151,7 @@ void scale_vector(float* v, float scale, size_t n) { } float convert_delta_vector_to_unit_vector(float* v) { - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; float magnitude = vector_length(v, n_axis); scale_vector(v, 1.0f / magnitude, n_axis); return magnitude; @@ -161,9 +161,9 @@ const float secPerMinSq = 60.0 * 60.0; // Seconds Per Minute Squared, for accel float limit_acceleration_by_axis_maximum(float* unit_vec) { float limit_value = SOME_LARGE_VALUE; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t idx = 0; idx < n_axis; idx++) { - auto axisSetting = config->_axes->_axis[idx]; + auto axisSetting = Axes::_axis[idx]; if (unit_vec[idx] != 0) { // Avoid divide by zero. limit_value = MIN(limit_value, fabsf(axisSetting->_acceleration / unit_vec[idx])); } @@ -177,9 +177,9 @@ float limit_acceleration_by_axis_maximum(float* unit_vec) { float limit_rate_by_axis_maximum(float* unit_vec) { float limit_value = SOME_LARGE_VALUE; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t idx = 0; idx < n_axis; idx++) { - auto axisSetting = config->_axes->_axis[idx]; + auto axisSetting = Axes::_axis[idx]; if (unit_vec[idx] != 0) { // Avoid divide by zero. limit_value = MIN(limit_value, fabsf(axisSetting->_maxRate / unit_vec[idx])); } diff --git a/FluidNC/src/OLED.cpp b/FluidNC/src/OLED.cpp index 7974f3faf..e5b8901e0 100644 --- a/FluidNC/src/OLED.cpp +++ b/FluidNC/src/OLED.cpp @@ -147,7 +147,7 @@ void OLED::show_dro(const float* axes, bool isMpos, bool* limits) { return; } - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; char axisVal[20]; show(limitLabelLayout, "L"); diff --git a/FluidNC/src/Pin.h b/FluidNC/src/Pin.h index b9bc9aa4f..9d6cf1d2d 100644 --- a/FluidNC/src/Pin.h +++ b/FluidNC/src/Pin.h @@ -95,7 +95,7 @@ class Pin { inline Pin(Pin&& o) : _detail(nullptr) { std::swap(_detail, o._detail); } inline Pin& operator=(const Pin& o) = delete; - inline Pin& operator =(Pin&& o) { + inline Pin& operator=(Pin&& o) { std::swap(_detail, o._detail); return *this; } diff --git a/FluidNC/src/Pins/ChannelPinDetail.cpp b/FluidNC/src/Pins/ChannelPinDetail.cpp index db944487f..84972c423 100644 --- a/FluidNC/src/Pins/ChannelPinDetail.cpp +++ b/FluidNC/src/Pins/ChannelPinDetail.cpp @@ -34,7 +34,9 @@ namespace Pins { s += std::to_string(high); _channel->out(s, "SET:"); } - int ChannelPinDetail::read() { return _value; } + int ChannelPinDetail::read() { + return _value; + } void ChannelPinDetail::setAttr(PinAttributes attr) { _attributes = _attributes | attr; @@ -61,8 +63,10 @@ namespace Pins { _channel->setAttr(_index, _attributes.has(Pins::PinAttributes::Input) ? &this->_value : nullptr, s, "INI:"); } - PinAttributes ChannelPinDetail::getAttr() const { return _attributes; } - std::string ChannelPinDetail::toString() { + PinAttributes ChannelPinDetail::getAttr() const { + return _attributes; + } + std::string ChannelPinDetail::toString() { std::string s = _channel->name(); s += "."; s += std::to_string(_index); @@ -78,5 +82,7 @@ namespace Pins { return s; } - void ChannelPinDetail::registerEvent(EventPin* obj) { _channel->registerEvent(_index, obj); } + void ChannelPinDetail::registerEvent(EventPin* obj) { + _channel->registerEvent(_index, obj); + } } diff --git a/FluidNC/src/Pins/DebugPinDetail.cpp b/FluidNC/src/Pins/DebugPinDetail.cpp index d32f7f9f7..19f205bd2 100644 --- a/FluidNC/src/Pins/DebugPinDetail.cpp +++ b/FluidNC/src/Pins/DebugPinDetail.cpp @@ -58,7 +58,9 @@ namespace Pins { _implementation->setAttr(value); } - PinAttributes DebugPinDetail::getAttr() const { return _implementation->getAttr(); } + PinAttributes DebugPinDetail::getAttr() const { + return _implementation->getAttr(); + } void DebugPinDetail::CallbackHandler::handle(void* arg) { auto handler = static_cast(arg); diff --git a/FluidNC/src/Pins/ErrorPinDetail.cpp b/FluidNC/src/Pins/ErrorPinDetail.cpp index 32a390c43..984e789be 100644 --- a/FluidNC/src/Pins/ErrorPinDetail.cpp +++ b/FluidNC/src/Pins/ErrorPinDetail.cpp @@ -8,11 +8,15 @@ namespace Pins { ErrorPinDetail::ErrorPinDetail(std::string_view descr) : PinDetail(0), _description(descr) {} - PinCapabilities ErrorPinDetail::capabilities() const { return PinCapabilities::Error; } + PinCapabilities ErrorPinDetail::capabilities() const { + return PinCapabilities::Error; + } #ifdef ESP32 - void ErrorPinDetail::write(int high) { log_error("Cannot write to pin " << _description.c_str() << ". The config is incorrect."); } - int ErrorPinDetail::read() { + void ErrorPinDetail::write(int high) { + log_error("Cannot write to pin " << _description.c_str() << ". The config is incorrect."); + } + int ErrorPinDetail::read() { log_error("Cannot read from pin " << _description.c_str() << ". The config is incorrect."); return false; } @@ -21,8 +25,10 @@ namespace Pins { } #else - void ErrorPinDetail::write(int high) { Assert(false, "Cannot write to an error pin."); } - int ErrorPinDetail::read() { + void ErrorPinDetail::write(int high) { + Assert(false, "Cannot write to an error pin."); + } + int ErrorPinDetail::read() { Assert(false, "Cannot read from an error pin."); return false; } @@ -31,7 +37,9 @@ namespace Pins { #endif - PinAttributes ErrorPinDetail::getAttr() const { return PinAttributes::None; } + PinAttributes ErrorPinDetail::getAttr() const { + return PinAttributes::None; + } std::string ErrorPinDetail::toString() { std::string s("ERROR_PIN (for "); diff --git a/FluidNC/src/Planner.cpp b/FluidNC/src/Planner.cpp index d7e99a366..54747b653 100644 --- a/FluidNC/src/Planner.cpp +++ b/FluidNC/src/Planner.cpp @@ -317,13 +317,13 @@ bool plan_buffer_line(float* target, plan_line_data_t* pl_data) { get_motor_steps(position_steps); } else { if (!block->is_jog && Homing::unhomed_axes()) { - log_info("Unhomed axes: " << config->_axes->maskToNames(Homing::unhomed_axes())); + log_info("Unhomed axes: " << Axes::maskToNames(Homing::unhomed_axes())); send_alarm(ExecAlarm::Unhomed); return false; } copyAxes(position_steps, pl.position); } - auto n_axis = config->_axes->_numberAxis; + auto n_axis = 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. // Also, compute individual axes distance for move and prep unit vector calculations. diff --git a/FluidNC/src/ProcessSettings.cpp b/FluidNC/src/ProcessSettings.cpp index 9bf29e37f..ce796cb56 100644 --- a/FluidNC/src/ProcessSettings.cpp +++ b/FluidNC/src/ProcessSettings.cpp @@ -289,7 +289,7 @@ static Error disable_alarm_lock(const char* value, AuthenticationLevel auth_leve return err; } Homing::set_all_axes_homed(); - config->_kinematics->releaseMotors(config->_axes->motorMask, config->_axes->hardLimitMask()); + config->_kinematics->releaseMotors(Axes::motorMask, Axes::hardLimitMask()); report_feedback_message(Message::AlarmUnlock); set_state(State::Idle); } @@ -379,10 +379,10 @@ static Error cmd_log_verbose(const char* value, AuthenticationLevel auth_level, static Error home(AxisMask axisMask, Channel& out) { if (axisMask != Machine::Homing::AllCycles) { // if not AllCycles we need to make sure the cycle is not prohibited // if there is a cycle it is the axis from $H - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (int axis = 0; axis < n_axis; axis++) { if (bitnum_is_true(axisMask, axis)) { - auto axisConfig = config->_axes->_axis[axis]; + auto axisConfig = Axes::_axis[axis]; auto homing = axisConfig->_homing; auto homing_allowed = homing && homing->_allow_single_axis; if (!homing_allowed) @@ -449,7 +449,7 @@ static Error home_all(const char* value, AuthenticationLevel auth_level, Channel return retval; } } - if (!config->_axes->namesToMask(value, requestedAxes)) { + if (!Axes::namesToMask(value, requestedAxes)) { return Error::InvalidValue; } } @@ -636,7 +636,7 @@ static Error motor_control(const char* value, bool disable) { } if (!value || *value == '\0') { log_info((disable ? "Dis" : "En") << "abling all motors"); - config->_axes->set_disable(disable); + Axes::set_disable(disable); return Error::Ok; } @@ -647,7 +647,7 @@ static Error motor_control(const char* value, bool disable) { return Error::InvalidStatement; } - for (int i = 0; i < config->_axes->_numberAxis; i++) { + for (int i = 0; i < Axes::_numberAxis; i++) { char axisName = axes->axisName(i); if (strchr(value, axisName) || strchr(value, tolower(axisName))) { @@ -666,7 +666,7 @@ static Error motor_enable(const char* value, AuthenticationLevel auth_level, Cha } static Error motors_init(const char* value, AuthenticationLevel auth_level, Channel& out) { - config->_axes->config_motors(); + Axes::config_motors(); return Error::Ok; } diff --git a/FluidNC/src/Protocol.cpp b/FluidNC/src/Protocol.cpp index 0cee4c87d..ea42de908 100644 --- a/FluidNC/src/Protocol.cpp +++ b/FluidNC/src/Protocol.cpp @@ -239,7 +239,7 @@ static void check_startup_state() { report_error_message(Message::ConfigAlarmLock); } else { // Perform some machine checks to make sure everything is good to go. - if (config->_start->_checkLimits && config->_axes->hasHardLimits()) { + if (config->_start->_checkLimits && Axes::hasHardLimits()) { if (limits_get_state()) { sys.state = State::Alarm; // Ensure alarm state is active. report_error_message(Message::CheckLimits); @@ -319,7 +319,7 @@ void protocol_main_loop() { if (idleEndTime && (getCpuTicks() - idleEndTime) > 0) { idleEndTime = 0; // - config->_axes->set_disable(true); + Axes::set_disable(true); } uint32_t newHeapSize = xPortGetFreeHeapSize(); if (newHeapSize < heapLowWater) { @@ -740,23 +740,23 @@ static void protocol_do_cycle_start() { void protocol_disable_steppers() { if (state_is(State::Homing)) { // Leave steppers enabled while homing - config->_axes->set_disable(false); + Axes::set_disable(false); return; } if (state_is(State::Sleep) || state_is(State::Alarm)) { // Disable steppers immediately in sleep or alarm state - config->_axes->set_disable(true); + Axes::set_disable(true); return; } - if (config->_stepping->_idleMsecs == 255) { + if (Stepping::_idleMsecs == 255) { // Leave steppers enabled if configured for "stay enabled" - config->_axes->set_disable(false); + Axes::set_disable(false); return; } // Otherwise, schedule stepper disable in a few milliseconds // unless a disable time has already been scheduled if (idleEndTime == 0) { - idleEndTime = usToEndTicks(config->_stepping->_idleMsecs * 1000); + idleEndTime = usToEndTicks(Stepping::_idleMsecs * 1000); // idleEndTime 0 means that a stepper disable is not scheduled. so if we happen to // land on 0 as an end time, just push it back by one microsecond to get off 0. if (idleEndTime == 0) { @@ -835,7 +835,7 @@ static void protocol_do_late_reset() { config->_coolant->stop(); protocol_disable_steppers(); - config->_stepping->reset(); + Stepping::reset(); // turn off all User I/O immediately config->_userOutputs->all_off(); @@ -1086,7 +1086,7 @@ static void protocol_do_limit(void* arg) { limit->isHard()) { mc_critical(ExecAlarm::HardLimit); } - log_debug("Limit switch tripped for " << config->_axes->axisName(limit->_axis) << " motor " << limit->_motorNum); + log_debug("Limit switch tripped for " << Axes::axisName(limit->_axis) << " motor " << limit->_motorNum); } static void protocol_do_fault_pin(void* arg) { if (inMotionState() || state_is(State::Idle) || state_is(State::Hold) || state_is(State::SafetyDoor)) { diff --git a/FluidNC/src/Report.cpp b/FluidNC/src/Report.cpp index 2eced1eed..c5644b394 100644 --- a/FluidNC/src/Report.cpp +++ b/FluidNC/src/Report.cpp @@ -82,7 +82,7 @@ static const int axesStringLen = coordStringLen * MAX_N_AXIS; // Sends the axis values to the output channel static std::string report_util_axis_values(const float* axis_value) { std::ostringstream msg; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t idx = 0; idx < n_axis; idx++) { int decimals; float value = axis_value[idx]; @@ -434,7 +434,7 @@ void addPinReport(char* status, char pinLetter) { void mpos_to_wpos(float* position) { float* wco = get_wco(); - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (int idx = 0; idx < n_axis; idx++) { position[idx] -= wco[idx]; } @@ -483,11 +483,11 @@ void report_recompute_pin_string() { MotorMask lim_pin_state = limits_get_state(); if (lim_pin_state) { - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (size_t axis = 0; axis < n_axis; axis++) { if (bitnum_is_true(lim_pin_state, Machine::Axes::motor_bit(axis, 0)) || bitnum_is_true(lim_pin_state, Machine::Axes::motor_bit(axis, 1))) { - report_pin_string += config->_axes->axisName(axis); + report_pin_string += Axes::axisName(axis); } } } diff --git a/FluidNC/src/Stepper.cpp b/FluidNC/src/Stepper.cpp index 6d62fc604..6c44bd6e0 100644 --- a/FluidNC/src/Stepper.cpp +++ b/FluidNC/src/Stepper.cpp @@ -24,7 +24,7 @@ static bool awake = false; // Stores the planner block Bresenham algorithm execution data for the segments in the segment // buffer. Normally, this buffer is partially in-use, but, for the worst case scenario, it will -// never exceed the number of accessible stepper buffer segments (config->_stepping->_segments-1). +// never exceed the number of accessible stepper buffer segments (Stepping::_segments-1). // NOTE: This data is copied from the prepped planner blocks so that the planner blocks may be // discarded when entirely consumed and completed by the segment buffer. Also, AMASS alters this // data for its own use. @@ -54,11 +54,11 @@ void Stepper::init() { if (st_block_buffer) { delete[] st_block_buffer; } - st_block_buffer = new st_block_t[config->_stepping->_segments - 1]; + st_block_buffer = new st_block_t[Stepping::_segments - 1]; if (segment_buffer) { delete[] segment_buffer; } - segment_buffer = new segment_t[config->_stepping->_segments]; + segment_buffer = new segment_t[Stepping::_segments]; } // Stepper ISR data struct. Contains the running data for the main stepper ISR. @@ -201,7 +201,7 @@ bool IRAM_ATTR Stepper::pulse_func() { if (!awake) { return false; } - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; config->_axes->step(st.step_outbits, st.dir_outbits); @@ -212,7 +212,7 @@ bool IRAM_ATTR Stepper::pulse_func() { // Initialize new step segment and load number of steps to execute st.exec_segment = &segment_buffer[segment_buffer_tail]; // Initialize step segment timing per step and load number of steps to execute. - config->_stepping->setTimerPeriod(st.exec_segment->isrPeriod); + Stepping::setTimerPeriod(st.exec_segment->isrPeriod); st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow. // If the new segment starts a new planner block, initialize stepper variables and counters. // NOTE: When the segment data index changes, this indicates a new planner block. @@ -247,18 +247,6 @@ bool IRAM_ATTR Stepper::pulse_func() { return false; // Nothing to do but exit. } } -#if 0 - // Check probing state. - if (probing && config->_probe->tripped()) { - probing = false; - auto axes = config->_axes; - for (int axis = 0; axis < n_axis; axis++) { - auto m = axes->_axis[axis]->_motors[0]; - probe_steps[axis] = m ? m->_steps : 0; - } - protocol_send_event_from_ISR(&motionCancelEvent); - } -#endif // Reset step out bits. st.step_outbits = 0; @@ -275,7 +263,7 @@ bool IRAM_ATTR Stepper::pulse_func() { if (st.step_count == 0) { // Segment is complete. Discard current segment and advance segment indexing. st.exec_segment = NULL; - segment_buffer_tail = segment_buffer_tail >= (config->_stepping->_segments - 1) ? 0 : segment_buffer_tail + 1; + segment_buffer_tail = segment_buffer_tail >= (Stepping::_segments - 1) ? 0 : segment_buffer_tail + 1; } config->_axes->unstep(); @@ -291,10 +279,10 @@ void Stepper::wake_up() { // Cancel any pending stepper disable protocol_cancel_disable_steppers(); // Enable stepper drivers. - config->_axes->set_disable(false); + Axes::set_disable(false); // Enable Stepping Driver Interrupt - config->_stepping->startTimer(); + Stepping::startTimer(); } void Stepper::go_idle() { @@ -371,7 +359,7 @@ void Stepper::parking_restore_buffer() { // Increments the step segment buffer block data ring buffer. static uint8_t next_block_index(uint8_t block_index) { block_index++; - return block_index == (config->_stepping->_segments - 1) ? 0 : block_index; + return block_index == (Stepping::_segments - 1) ? 0 : block_index; } /* Prepares step segment buffer. Continuously called from main program. @@ -423,7 +411,7 @@ void Stepper::prep_buffer() { st_prep_block = &st_block_buffer[prep.st_block_index]; st_prep_block->direction_bits = pl_block->direction_bits; uint8_t idx; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; // Bit-shift multiply all Bresenham data by the max AMASS level so that // we never divide beyond the original data anywhere in the algorithm. @@ -746,7 +734,7 @@ void Stepper::prep_buffer() { // Segment complete! Increment segment buffer indices, so stepper ISR can immediately execute it. auto lastseg = segment_next_head; - segment_next_head = segment_next_head >= (config->_stepping->_segments - 1) ? 0 : segment_next_head + 1; + segment_next_head = segment_next_head >= (Stepping::_segments - 1) ? 0 : segment_next_head + 1; segment_buffer_head = lastseg; // Update the appropriate planner and segment data. diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index edd8a28e4..0df329e0a 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -9,6 +9,14 @@ namespace Machine { int Stepping::_engine = RMT; + bool Stepping::_switchedStepper = false; + int32_t Stepping::_stepPulseEndTime; + size_t Stepping::_segments = 12; + + uint32_t Stepping::_idleMsecs = 255; + uint32_t Stepping::_pulseUsecs = 4; + uint32_t Stepping::_directionDelayUsecs = 0; + uint32_t Stepping::_disableDelayUsecs = 0; const EnumItem stepTypes[] = { { Stepping::TIMED, "Timed" }, { Stepping::RMT, "RMT" }, diff --git a/FluidNC/src/Stepping.h b/FluidNC/src/Stepping.h index f4af242ec..0dfb25f6f 100644 --- a/FluidNC/src/Stepping.h +++ b/FluidNC/src/Stepping.h @@ -14,12 +14,11 @@ namespace Machine { static const uint32_t fStepperTimer = 20000000; // frequency of step pulse timer private: - static bool onStepperDriverTimer(); - static const int ticksPerMicrosecond = fStepperTimer / 1000000; - bool _switchedStepper = false; - int32_t _stepPulseEndTime; + static bool _switchedStepper; + static int32_t _stepPulseEndTime; + public: enum stepper_id_t { @@ -38,32 +37,38 @@ namespace Machine { // execution lead time there is for other processes to run. The latency for a feedhold or other // override is roughly 10 ms times _segments. - size_t _segments = 12; + static size_t _segments; - uint32_t _idleMsecs = 255; - uint32_t _pulseUsecs = 4; - uint32_t _directionDelayUsecs = 0; - uint32_t _disableDelayUsecs = 0; + static uint32_t _idleMsecs; + static uint32_t _pulseUsecs; + static uint32_t _directionDelayUsecs; + static uint32_t _disableDelayUsecs; static int _engine; // Interfaces to stepping engine - void init(); + static void init(); + + static void assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert); + + static void setStepPin(int axis, int motor, int pin, bool invert); + static void setDirPin(int axis, int motor, int pin, bool invert); + + static void reset(); // Clean up old state and start fresh + static void beginLowLatency(); + static void endLowLatency(); + static void startPulseTimer(); + static void waitPulse(); // Wait for pulse length + static void waitDirection(); // Wait for direction delay + static void waitMotion(); // Wait for motion to complete + static void finishPulse(); // Cleanup after unstep - void reset(); // Clean up old state and start fresh - void beginLowLatency(); - void endLowLatency(); - void startPulseTimer(); - void waitPulse(); // Wait for pulse length - void waitDirection(); // Wait for direction delay - void waitMotion(); // Wait for motion to complete - void finishPulse(); // Cleanup after unstep - uint32_t maxPulsesPerSec(); + static uint32_t maxPulsesPerSec(); // Timers - void setTimerPeriod(uint16_t timerTicks); - void startTimer(); + static void setTimerPeriod(uint16_t timerTicks); + static void startTimer(); static void stopTimer(); // Configuration system helpers: diff --git a/FluidNC/src/System.cpp b/FluidNC/src/System.cpp index 1a33c6da5..907f48e93 100644 --- a/FluidNC/src/System.cpp +++ b/FluidNC/src/System.cpp @@ -34,10 +34,10 @@ void system_reset() { } float steps_to_mpos(int32_t steps, size_t axis) { - return float(steps / config->_axes->_axis[axis]->_stepsPerMm); + return float(steps / Axes::_axis[axis]->_stepsPerMm); } int32_t mpos_to_steps(float mpos, size_t axis) { - return lroundf(mpos * config->_axes->_axis[axis]->_stepsPerMm); + return lroundf(mpos * Axes::_axis[axis]->_stepsPerMm); } void motor_steps_to_mpos(float* position, int32_t* steps) { @@ -98,7 +98,7 @@ float* get_mpos() { float* get_wco() { static float wco[MAX_N_AXIS]; - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; for (int idx = 0; idx < n_axis; idx++) { // Apply work coordinate offsets and tool length offset to current position. wco[idx] = gc_state.coord_system[idx] + gc_state.coord_offset[idx]; diff --git a/FluidNC/src/WebUI/WifiConfig.cpp b/FluidNC/src/WebUI/WifiConfig.cpp index b32d2edee..7bf85c294 100644 --- a/FluidNC/src/WebUI/WifiConfig.cpp +++ b/FluidNC/src/WebUI/WifiConfig.cpp @@ -278,8 +278,8 @@ namespace WebUI { j.id_value_object("SSID: ", (const char*)conf.ap.ssid); j.id_value_object("Visible: ", (conf.ap.ssid_hidden == 0 ? "Yes" : "No")); j.id_value_object("Radio country set: ", - std::string("") + country.cc[0] + country.cc[1] + " (channels " + std::to_string(country.schan) + "-" + - std::to_string((country.schan + country.nchan - 1)) + ", max power " + + std::string("") + country.cc[0] + country.cc[1] + " (channels " + std::to_string(country.schan) + + "-" + std::to_string((country.schan + country.nchan - 1)) + ", max power " + std::to_string(country.max_tx_power) + "dBm)"); const char* mode; @@ -520,7 +520,7 @@ namespace WebUI { j.member("FlashFileSystem", "LittleFS"); j.member("HostPath", "/"); j.member("Time", "none"); - j.member("Axisletters", config->_axes->_names); + j.member("Axisletters", Axes::_names); j.end_object(); j.end(); return Error::Ok; @@ -582,7 +582,7 @@ namespace WebUI { } //to save time in decoding `?` - s << " # axis:" << config->_axes->_numberAxis; + s << " # axis:" << Axes::_numberAxis; return Error::Ok; } From 921f32bed06ad6bce24d5a49f3040b628a963aba Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Fri, 4 Oct 2024 10:46:12 -1000 Subject: [PATCH 02/30] Consolidated stepping engine code Avoids the need to move Motor and Pin vtables into RAM, thus saving about 20K of precious RAM space. It also simplifies the structure of the stepping code dramatically, so it is all in one place instead of being distributed throughout a complex class hierarchy. --- FluidNC/ld/esp32/README.md | 7 - FluidNC/ld/esp32/bt/sections.ld | 2 - FluidNC/ld/esp32/noradio/sections.ld | 7 +- FluidNC/ld/esp32/vtable_in_dram.ld | 26 +- FluidNC/ld/esp32/wifi/sections.ld | 7 +- FluidNC/src/Kinematics/Cartesian.cpp | 5 +- FluidNC/src/Kinematics/CoreXY.cpp | 2 +- FluidNC/src/Machine/Axes.cpp | 58 +---- FluidNC/src/Machine/Homing.cpp | 8 +- FluidNC/src/Machine/LimitPin.cpp | 12 +- FluidNC/src/Machine/LimitPin.h | 4 +- FluidNC/src/Machine/Motor.cpp | 23 +- FluidNC/src/Machine/Motor.h | 9 - FluidNC/src/Motors/MotorDriver.cpp | 7 +- FluidNC/src/Motors/MotorDriver.h | 16 -- FluidNC/src/Motors/StandardStepper.cpp | 99 ++------ FluidNC/src/Motors/StandardStepper.h | 12 - FluidNC/src/Motors/UnipolarMotor.cpp | 128 ---------- FluidNC/src/Motors/UnipolarMotor.h | 49 ---- FluidNC/src/Pin.h | 3 + FluidNC/src/Pins/GPIOPinDetail.cpp | 10 +- FluidNC/src/Pins/GPIOPinDetail.h | 3 +- FluidNC/src/Pins/I2SOPinDetail.cpp | 13 +- FluidNC/src/Pins/I2SOPinDetail.h | 4 +- FluidNC/src/Pins/PinDetail.h | 11 +- .../Spindles/{Laser.cpp => LaserSpindle.cpp} | 2 +- .../src/Spindles/{Laser.h => LaserSpindle.h} | 0 FluidNC/src/Stepper.cpp | 8 +- FluidNC/src/Stepping.cpp | 224 ++++++++++++++++-- FluidNC/src/Stepping.h | 43 +++- FluidNC/src/System.cpp | 17 +- 31 files changed, 315 insertions(+), 504 deletions(-) delete mode 100644 FluidNC/src/Motors/UnipolarMotor.cpp delete mode 100644 FluidNC/src/Motors/UnipolarMotor.h rename FluidNC/src/Spindles/{Laser.cpp => LaserSpindle.cpp} (98%) rename FluidNC/src/Spindles/{Laser.h => LaserSpindle.h} (100%) diff --git a/FluidNC/ld/esp32/README.md b/FluidNC/ld/esp32/README.md index 38b7ea99e..b23cca0a8 100644 --- a/FluidNC/ld/esp32/README.md +++ b/FluidNC/ld/esp32/README.md @@ -57,13 +57,6 @@ vtables from several code modules to be placed in RAM. We use the INCLUDE technique to isolate the changes into one new file, for easier maintenance. -For the wifi and noradio builds, the vtables are placed in IRAM because -there is enough free space there, freeing up some DRAM to use as heap. -For the bt build, bluetooth functions use so much IRAM that there is -not enough room for the vtables, so they are placed into DRAM. The -bt version does not need as much heap as the wifi version (wifi needs -quite a few large buffers for wifi packets). - ### Making PlatformIO Use the Modified File To make PlatformIO use the modified sections.ld instead diff --git a/FluidNC/ld/esp32/bt/sections.ld b/FluidNC/ld/esp32/bt/sections.ld index 50f150df4..b75ea73df 100644 --- a/FluidNC/ld/esp32/bt/sections.ld +++ b/FluidNC/ld/esp32/bt/sections.ld @@ -558,8 +558,6 @@ SECTIONS *libspi_flash.a:spi_flash_chip_winbond.*(.rodata .rodata.*) *libspi_flash.a:spi_flash_rom_patch.*(.rodata .rodata.*) - /* For the bluetooth build we must use DRAM for the ISR vtables */ - /* because there is not enough space left in IRAM */ INCLUDE ../vtable_in_dram.ld _data_end = ABSOLUTE(.); diff --git a/FluidNC/ld/esp32/noradio/sections.ld b/FluidNC/ld/esp32/noradio/sections.ld index 6b5f7e18d..b75ea73df 100644 --- a/FluidNC/ld/esp32/noradio/sections.ld +++ b/FluidNC/ld/esp32/noradio/sections.ld @@ -558,6 +558,8 @@ SECTIONS *libspi_flash.a:spi_flash_chip_winbond.*(.rodata .rodata.*) *libspi_flash.a:spi_flash_rom_patch.*(.rodata .rodata.*) + INCLUDE ../vtable_in_dram.ld + _data_end = ABSOLUTE(.); . = ALIGN(4); } > dram0_0_seg @@ -775,11 +777,6 @@ SECTIONS *(.iram2.coredump .iram2.coredump.*) _coredump_iram_end = ABSOLUTE(.); - /* For the wifi build we can use IRAM for the ISR vtables */ - /* because there is enough space left there, conserving */ - /* DRAM which is needed for extra heap space */ - INCLUDE ../vtable_in_dram.ld - _iram_data_end = ABSOLUTE(.); } > iram0_0_seg diff --git a/FluidNC/ld/esp32/vtable_in_dram.ld b/FluidNC/ld/esp32/vtable_in_dram.ld index ff51ecba0..0a662d6c2 100644 --- a/FluidNC/ld/esp32/vtable_in_dram.ld +++ b/FluidNC/ld/esp32/vtable_in_dram.ld @@ -1,28 +1,8 @@ /* List of files and sections to place in RAM instead of FLASH */ /* See README.md in this directory for a complete explanation */ - *Laser.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - /* All files whose name ends with Spindle.cpp */ - **Spindle.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - - /* Motors */ - *Motor.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *Dynamixel2.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *MotorDriver.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *NullMotor.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *RcServo.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *Servo.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *Solenoid.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *StandardStepper.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *StepStick.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *TMC*Driver.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *TrinamicBase.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *TrinamicSpiDriver.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *TrinamicUartDriver.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *UnipolarMotor.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - /* Pin Details */ - **Detail.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *PwmPin.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) - *Pin.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*) + /* An earlier version included .xt.prop and .xt.prop.* but */ + /* it turns out that those bits can stay in FLASH */ + **Spindle.cpp.o(.rodata .rodata.*) diff --git a/FluidNC/ld/esp32/wifi/sections.ld b/FluidNC/ld/esp32/wifi/sections.ld index 6b5f7e18d..b75ea73df 100644 --- a/FluidNC/ld/esp32/wifi/sections.ld +++ b/FluidNC/ld/esp32/wifi/sections.ld @@ -558,6 +558,8 @@ SECTIONS *libspi_flash.a:spi_flash_chip_winbond.*(.rodata .rodata.*) *libspi_flash.a:spi_flash_rom_patch.*(.rodata .rodata.*) + INCLUDE ../vtable_in_dram.ld + _data_end = ABSOLUTE(.); . = ALIGN(4); } > dram0_0_seg @@ -775,11 +777,6 @@ SECTIONS *(.iram2.coredump .iram2.coredump.*) _coredump_iram_end = ABSOLUTE(.); - /* For the wifi build we can use IRAM for the ISR vtables */ - /* because there is enough space left there, conserving */ - /* DRAM which is needed for extra heap space */ - INCLUDE ../vtable_in_dram.ld - _iram_data_end = ABSOLUTE(.); } > iram0_0_seg diff --git a/FluidNC/src/Kinematics/Cartesian.cpp b/FluidNC/src/Kinematics/Cartesian.cpp index b318f5dc5..f45b614db 100644 --- a/FluidNC/src/Kinematics/Cartesian.cpp +++ b/FluidNC/src/Kinematics/Cartesian.cpp @@ -294,12 +294,11 @@ namespace Kinematics { auto n_axis = axes->_numberAxis; for (int axis = 0; axis < n_axis; axis++) { if (bitnum_is_true(axisMask, axis)) { - auto paxis = axes->_axis[axis]; if (bitnum_is_true(motors, Machine::Axes::motor_bit(axis, 0))) { - paxis->_motors[0]->unlimit(); + Stepping::unlimit(axis, 0); } if (bitnum_is_true(motors, Machine::Axes::motor_bit(axis, 1))) { - paxis->_motors[1]->unlimit(); + Stepping::unlimit(axis, 1); } } } diff --git a/FluidNC/src/Kinematics/CoreXY.cpp b/FluidNC/src/Kinematics/CoreXY.cpp index 7a531b9f3..c3e9b80d9 100644 --- a/FluidNC/src/Kinematics/CoreXY.cpp +++ b/FluidNC/src/Kinematics/CoreXY.cpp @@ -75,7 +75,7 @@ namespace Kinematics { auto n_axis = axes->_numberAxis; for (size_t axis = X_AXIS; axis < n_axis; axis++) { if (bitnum_is_true(axisMask, axis)) { - axes->_axis[axis]->_motors[0]->unlimit(); + Stepping::unlimit(axis, 0); } } } diff --git a/FluidNC/src/Machine/Axes.cpp b/FluidNC/src/Machine/Axes.cpp index 0fff03c1d..dd47ddf2e 100644 --- a/FluidNC/src/Machine/Axes.cpp +++ b/FluidNC/src/Machine/Axes.cpp @@ -93,9 +93,9 @@ namespace Machine { auto a = _axis[axis]; if (a != nullptr) { for (size_t motor = 0; motor < Axis::MAX_MOTORS_PER_AXIS; motor++) { + Stepping::unblock(axis, motor); auto m = _axis[axis]->_motors[motor]; if (m) { - m->unblock(); if (m->_driver->set_homing_mode(isHoming)) { set_bitnum(motorsCanHome, motor_bit(axis, motor)); } @@ -108,62 +108,6 @@ namespace Machine { return motorsCanHome; } - void IRAM_ATTR Axes::step(uint8_t step_mask, uint8_t dir_mask) { - auto n_axis = _numberAxis; - //log_info("motors_set_direction_pins:0x%02X", onMask); - - // Set the direction pins, but optimize for the common - // situation where the direction bits haven't changed. - static uint8_t previous_dir = 255; // should never be this value - if (dir_mask != previous_dir) { - previous_dir = dir_mask; - - for (int axis = X_AXIS; axis < n_axis; axis++) { - bool thisDir = bitnum_is_true(dir_mask, axis); - - for (size_t motor = 0; motor < Axis::MAX_MOTORS_PER_AXIS; motor++) { - auto m = _axis[axis]->_motors[motor]; - if (m) { - m->_driver->set_direction(thisDir); - } - } - } - config->_stepping->waitDirection(); - } - - // Turn on step pulses for motors that are supposed to step now - for (size_t axis = X_AXIS; axis < n_axis; axis++) { - if (bitnum_is_true(step_mask, axis)) { - bool dir = bitnum_is_true(dir_mask, axis); - - auto a = _axis[axis]; - for (size_t motor = 0; motor < Axis::MAX_MOTORS_PER_AXIS; motor++) { - auto m = a->_motors[motor]; - if (m) { - m->step(dir); - } - } - } - } - config->_stepping->startPulseTimer(); - } - - // Turn all stepper pins off - void IRAM_ATTR Axes::unstep() { - config->_stepping->waitPulse(); - auto n_axis = _numberAxis; - for (size_t axis = X_AXIS; axis < n_axis; axis++) { - for (size_t motor = 0; motor < Axis::MAX_MOTORS_PER_AXIS; motor++) { - auto m = _axis[axis]->_motors[motor]; - if (m) { - m->_driver->unstep(); - } - } - } - - config->_stepping->finishPulse(); - } - void Axes::config_motors() { for (int axis = 0; axis < _numberAxis; ++axis) { _axis[axis]->config_motors(); diff --git a/FluidNC/src/Machine/Homing.cpp b/FluidNC/src/Machine/Homing.cpp index 470c200fe..577c6fe2e 100644 --- a/FluidNC/src/Machine/Homing.cpp +++ b/FluidNC/src/Machine/Homing.cpp @@ -199,11 +199,11 @@ namespace Machine { travel = axisConfig->extraPulloff(); if (travel < 0) { // Motor0's pulloff is greater than motor1's, so we block motor1 - axisConfig->_motors[1]->block(); + Stepping::block(axis, 1); travel = -travel; } else if (travel > 0) { // Motor1's pulloff is greater than motor0's, so we block motor0 - axisConfig->_motors[0]->block(); + Stepping::block(axis, 0); } // All motors will be unblocked later by set_homing_mode() break; @@ -357,7 +357,7 @@ namespace Machine { gc_sync_position(); plan_sync_position(); - config->_stepping->endLowLatency(); + Stepping::endLowLatency(); if (!sys.abort) { set_state(unhomed_axes() ? State::Alarm : State::Idle); @@ -521,7 +521,7 @@ namespace Machine { set_state(State::Alarm); return; } - config->_stepping->beginLowLatency(); + Stepping::beginLowLatency(); set_state(State::Homing); nextCycle(); diff --git a/FluidNC/src/Machine/LimitPin.cpp b/FluidNC/src/Machine/LimitPin.cpp index f2b4b96e7..ccc3adeec 100644 --- a/FluidNC/src/Machine/LimitPin.cpp +++ b/FluidNC/src/Machine/LimitPin.cpp @@ -6,9 +6,9 @@ #include "src/Protocol.h" // protocol_send_event_from_ISR() namespace Machine { - LimitPin::LimitPin(Pin& pin, int axis, int motor, int direction, bool& pHardLimits, bool& pLimited) : - EventPin(&limitEvent, "Limit"), _axis(axis), _motorNum(motor), _value(false), _pHardLimits(pHardLimits), _pLimited(pLimited), - _pin(&pin) { + LimitPin::LimitPin(Pin& pin, int axis, int motor, int direction, bool& pHardLimits) : + EventPin(&limitEvent, "Limit"), _axis(axis), _motorNum(motor), _value(false), _pHardLimits(pHardLimits), _pin(&pin) { + _pLimited = Stepping::limit_var(axis, motor); const char* sDir; // Select one or two bitmask variables to receive the switch data switch (direction) { @@ -56,7 +56,7 @@ namespace Machine { void LimitPin::update(bool value) { if (value) { if (Homing::approach() || (!state_is(State::Homing) && _pHardLimits)) { - _pLimited = value; + *_pLimited = value; if (_pExtraLimited != nullptr) { *_pExtraLimited = value; @@ -70,7 +70,7 @@ namespace Machine { set_bits(*_negLimits, _bitmask); } } else { - _pLimited = value; + *_pLimited = value; if (_pExtraLimited != nullptr) { *_pExtraLimited = value; @@ -92,6 +92,6 @@ namespace Machine { } void LimitPin::setExtraMotorLimit(int axis, int motorNum) { - _pExtraLimited = &config->_axes->_axis[axis]->_motors[motorNum]->_limited; + _pExtraLimited = Stepping::limit_var(axis, motorNum); } } diff --git a/FluidNC/src/Machine/LimitPin.h b/FluidNC/src/Machine/LimitPin.h index 75d02e4fd..13eb5578f 100644 --- a/FluidNC/src/Machine/LimitPin.h +++ b/FluidNC/src/Machine/LimitPin.h @@ -19,7 +19,7 @@ namespace Machine { // touch, increasing the accuracy of homing // _pExtraLimited lets the limit control two motors, as with // CoreXY - volatile bool& _pLimited; + volatile bool* _pLimited; volatile bool* _pExtraLimited = nullptr; volatile uint32_t* _posLimits = nullptr; @@ -28,7 +28,7 @@ namespace Machine { Pin* _pin; public: - LimitPin(Pin& pin, int axis, int motorNum, int direction, bool& phardLimits, bool& pLimited); + LimitPin(Pin& pin, int axis, int motorNum, int direction, bool& phardLimits); void update(bool value) override; diff --git a/FluidNC/src/Machine/Motor.cpp b/FluidNC/src/Machine/Motor.cpp index a6e0eb566..770240aea 100644 --- a/FluidNC/src/Machine/Motor.cpp +++ b/FluidNC/src/Machine/Motor.cpp @@ -31,15 +31,13 @@ namespace Machine { } _driver->init(); - _negLimitPin = new LimitPin(_negPin, _axis, _motorNum, -1, _hardLimits, _limited); - _posLimitPin = new LimitPin(_posPin, _axis, _motorNum, 1, _hardLimits, _limited); - _allLimitPin = new LimitPin(_allPin, _axis, _motorNum, 0, _hardLimits, _limited); + _negLimitPin = new LimitPin(_negPin, _axis, _motorNum, -1, _hardLimits); + _posLimitPin = new LimitPin(_posPin, _axis, _motorNum, 1, _hardLimits); + _allLimitPin = new LimitPin(_allPin, _axis, _motorNum, 0, _hardLimits); _negLimitPin->init(); _posLimitPin->init(); _allLimitPin->init(); - - unblock(); } void Motor::config_motor() { @@ -71,21 +69,6 @@ namespace Machine { return _driver->isReal(); } - void IRAM_ATTR Motor::step(bool reverse) { - // Skip steps based on limit pins - // _blocked is for asymmetric pulloff - // _limited is for limit pins - if (_blocked || _limited) { - return; - } - _driver->step(); - _steps += reverse ? -1 : 1; - } - - void IRAM_ATTR Motor::unstep() { - _driver->unstep(); - } - Motor::~Motor() { delete _driver; } diff --git a/FluidNC/src/Machine/Motor.h b/FluidNC/src/Machine/Motor.h index fb14faca7..72e25f147 100644 --- a/FluidNC/src/Machine/Motor.h +++ b/FluidNC/src/Machine/Motor.h @@ -35,10 +35,6 @@ namespace Machine { Pin _allPin; bool _hardLimits = false; - int32_t _steps = 0; - bool _limited = false; // _limited is set by the LimitPin ISR - bool _blocked = false; // _blocked is used during asymmetric homing pulloff - // Configuration system helpers: void group(Configuration::HandlerBase& handler) override; void afterParse() override; @@ -48,11 +44,6 @@ namespace Machine { void limitOtherAxis(int axis); void init(); void config_motor(); - void step(bool reverse); - void unstep(); - void block() { _blocked = true; } - void unblock() { _blocked = false; } - void unlimit() { _limited = false; } ~Motor(); }; } diff --git a/FluidNC/src/Motors/MotorDriver.cpp b/FluidNC/src/Motors/MotorDriver.cpp index 3c7988f2d..ab2edee6c 100644 --- a/FluidNC/src/Motors/MotorDriver.cpp +++ b/FluidNC/src/Motors/MotorDriver.cpp @@ -38,14 +38,11 @@ namespace MotorDrivers { size_t MotorDriver::axis_index() const { Assert(config != nullptr && config->_axes != nullptr, "Expected machine to be configured before this is called."); - return size_t(config->_axes->findAxisIndex(this)); + return size_t(Axes::findAxisIndex(this)); } size_t MotorDriver::dual_axis_index() const { Assert(config != nullptr && config->_axes != nullptr, "Expected machine to be configured before this is called."); - return size_t(config->_axes->findAxisMotor(this)); + return size_t(Axes::findAxisMotor(this)); } void IRAM_ATTR MotorDriver::set_disable(bool disable) {} - void IRAM_ATTR MotorDriver::set_direction(bool) {} - void IRAM_ATTR MotorDriver::step() {} - void IRAM_ATTR MotorDriver::unstep() {} } diff --git a/FluidNC/src/Motors/MotorDriver.h b/FluidNC/src/Motors/MotorDriver.h index db8f57e48..c62e03c24 100644 --- a/FluidNC/src/Motors/MotorDriver.h +++ b/FluidNC/src/Motors/MotorDriver.h @@ -64,22 +64,6 @@ namespace MotorDrivers { // make a motor transition between idle and non-idle states. virtual void set_disable(bool disable); - // set_direction() sets the motor movement direction. It is - // invoked for every motion segment. - virtual void set_direction(bool); - - // step() initiates a step operation on a motor. It is called - // from motors_step() for ever motor than needs to step now. - // For ordinary step/direction motors, it sets the step pin - // to the active state. - virtual void step(); - - // unstep() turns off the step pin, if applicable, for a motor. - // It is called from motors_unstep() for all motors, since - // motors_unstep() is used in many contexts where the previous - // states of the step pins are unknown. - virtual void unstep(); - // this is used to configure and test motors. This would be used for Trinamic virtual void config_motor() {} diff --git a/FluidNC/src/Motors/StandardStepper.cpp b/FluidNC/src/Motors/StandardStepper.cpp index 20b3aec2b..e66c9c961 100644 --- a/FluidNC/src/Motors/StandardStepper.cpp +++ b/FluidNC/src/Motors/StandardStepper.cpp @@ -9,7 +9,7 @@ #include "../Machine/MachineConfig.h" #include "../Stepper.h" // ST_I2S_* -#include "../Stepping.h" // config->_stepping->_engine +#include "../Stepping.h" // Stepping::_engine #include // gpio #include // CONFIG_IDF_TARGET_* @@ -18,108 +18,41 @@ using namespace Machine; namespace MotorDrivers { - static void init_rmt_channel(rmt_channel_t& rmt_chan_num, Pin& step_pin, bool invert_step, uint32_t dir_delay_ms, uint32_t pulse_us) { - static rmt_channel_t next_RMT_chan_num = RMT_CHANNEL_0; - if (rmt_chan_num == RMT_CHANNEL_MAX) { - if (next_RMT_chan_num == RMT_CHANNEL_MAX) { - log_error("Out of RMT channels"); - return; - } - rmt_chan_num = next_RMT_chan_num; - next_RMT_chan_num = static_cast(static_cast(next_RMT_chan_num) + 1); - } - - auto step_pin_gpio = step_pin.getNative(Pin::Capabilities::Output); - - rmt_config_t rmtConfig = { .rmt_mode = RMT_MODE_TX, - .channel = rmt_chan_num, - .gpio_num = gpio_num_t(step_pin_gpio), - .clk_div = 20, - .mem_block_num = 2, - .flags = 0, - .tx_config = { - .carrier_freq_hz = 0, - .carrier_level = RMT_CARRIER_LEVEL_LOW, - .idle_level = invert_step ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW, - .carrier_duty_percent = 50, -#if SOC_RMT_SUPPORT_TX_LOOP_COUNT - .loop_count = 1, -#endif - .carrier_en = false, - .loop_en = false, - .idle_output_en = true, - } }; - - rmt_item32_t rmtItem[2]; - rmtItem[0].duration0 = dir_delay_ms ? dir_delay_ms * 4 : 1; - rmtItem[0].duration1 = 4 * pulse_us; - rmtItem[1].duration0 = 0; - rmtItem[1].duration1 = 0; - - rmtItem[0].level0 = rmtConfig.tx_config.idle_level; - rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; - rmt_config(&rmtConfig); - rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); - } - void StandardStepper::init() { read_settings(); config_message(); } - void StandardStepper::read_settings() { init_step_dir_pins(); } + void StandardStepper::read_settings() { + init_step_dir_pins(); + } void StandardStepper::init_step_dir_pins() { - auto axisIndex = axis_index(); - - _invert_step = _step_pin.getAttr().has(Pin::Attr::ActiveLow); - _invert_disable = _disable_pin.getAttr().has(Pin::Attr::ActiveLow); + auto axisIndex = axis_index(); + auto dualAxisIndex = dual_axis_index(); + _step_pin.setAttr(Pin::Attr::Output); _dir_pin.setAttr(Pin::Attr::Output); - auto stepping = config->_stepping; - if (stepping->_engine == Stepping::RMT) { - init_rmt_channel(_rmt_chan_num, _step_pin, _invert_step, stepping->_directionDelayUsecs, stepping->_pulseUsecs); - } else { - _step_pin.setAttr(Pin::Attr::Output); - } - if (_disable_pin.defined()) { _disable_pin.setAttr(Pin::Attr::Output); } + + if (_step_pin.canStep()) { + Stepping::assignMotor(axisIndex, dualAxisIndex, _step_pin.index(), _step_pin.inverted(), _dir_pin.index(), _dir_pin.inverted()); + } + + auto dir_gpio = _step_pin.getNative(Pin::Capabilities::Output); } void StandardStepper::config_message() { log_info(" " << name() << " Step:" << _step_pin.name() << " Dir:" << _dir_pin.name() << " Disable:" << _disable_pin.name()); } - void IRAM_ATTR StandardStepper::step() { - if (config->_stepping->_engine == Stepping::RMT && _rmt_chan_num != RMT_CHANNEL_MAX) { -#ifdef CONFIG_IDF_TARGET_ESP32 - RMT.conf_ch[_rmt_chan_num].conf1.mem_rd_rst = 1; - RMT.conf_ch[_rmt_chan_num].conf1.mem_rd_rst = 0; - RMT.conf_ch[_rmt_chan_num].conf1.tx_start = 1; -#endif -#ifdef CONFIG_IDF_TARGET_ESP32S3 - RMT.chnconf0[_rmt_chan_num].mem_rd_rst_n = 1; - RMT.chnconf0[_rmt_chan_num].mem_rd_rst_n = 0; - RMT.chnconf0[_rmt_chan_num].tx_start_n = 1; -#endif - } else { - _step_pin.on(); - } - } - - void IRAM_ATTR StandardStepper::unstep() { - if (config->_stepping->_engine != Stepping::RMT) { - _step_pin.off(); - } + void IRAM_ATTR StandardStepper::set_disable(bool disable) { + _disable_pin.synchronousWrite(disable); } - void IRAM_ATTR StandardStepper::set_direction(bool dir) { _dir_pin.write(dir); } - - void IRAM_ATTR StandardStepper::set_disable(bool disable) { _disable_pin.synchronousWrite(disable); } - // Configuration registration namespace { MotorFactory::InstanceBuilder registration("standard_stepper"); @@ -127,7 +60,7 @@ namespace MotorDrivers { void StandardStepper::validate() { Assert(_step_pin.defined(), "Step pin must be configured."); - bool isI2SO = config->_stepping->_engine == Stepping::I2S_STREAM || config->_stepping->_engine == Stepping::I2S_STATIC; + bool isI2SO = Stepping::_engine == Stepping::I2S_STREAM || Stepping::_engine == Stepping::I2S_STATIC; if (isI2SO) { Assert(_step_pin.name().rfind("I2SO", 0) == 0, "Step pin must be an I2SO pin"); if (_dir_pin.defined()) { diff --git a/FluidNC/src/Motors/StandardStepper.h b/FluidNC/src/Motors/StandardStepper.h index e74759415..fb060f7ef 100644 --- a/FluidNC/src/Motors/StandardStepper.h +++ b/FluidNC/src/Motors/StandardStepper.h @@ -2,8 +2,6 @@ #include "MotorDriver.h" -#include - namespace MotorDrivers { class StandardStepper : public MotorDriver { public: @@ -17,9 +15,6 @@ namespace MotorDrivers { // No special action, but return true to say homing is possible bool set_homing_mode(bool isHoming) override { return true; } void set_disable(bool) override; - void set_direction(bool) override; - void step() override; - void unstep() override; void read_settings() override; void init_step_dir_pins(); @@ -39,12 +34,5 @@ namespace MotorDrivers { handler.item("direction_pin", _dir_pin); handler.item("disable_pin", _disable_pin); } - - private: - // Initialized after configuration for RMT steps: - bool _invert_step; - bool _invert_disable; - - rmt_channel_t _rmt_chan_num = RMT_CHANNEL_MAX; }; } diff --git a/FluidNC/src/Motors/UnipolarMotor.cpp b/FluidNC/src/Motors/UnipolarMotor.cpp deleted file mode 100644 index f1b14dde9..000000000 --- a/FluidNC/src/Motors/UnipolarMotor.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2020 - Bart Dring -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#include "UnipolarMotor.h" -#include "../Machine/MachineConfig.h" - -namespace MotorDrivers { - void UnipolarMotor::init() { - _pin_phase0.setAttr(Pin::Attr::Output); - _pin_phase1.setAttr(Pin::Attr::Output); - _pin_phase2.setAttr(Pin::Attr::Output); - _pin_phase3.setAttr(Pin::Attr::Output); - _current_phase = 0; - config_message(); - } - - void UnipolarMotor::config_message() { - log_info(" " << name() << " Ph0:" << _pin_phase0.name() << " Ph1:" << _pin_phase1.name() << " Ph2:" << _pin_phase2.name() - << " Ph3:" << _pin_phase3.name()); - } - - void IRAM_ATTR UnipolarMotor::set_disable(bool disable) { - if (disable) { - _pin_phase0.off(); - _pin_phase1.off(); - _pin_phase2.off(); - _pin_phase3.off(); - } - _enabled = !disable; - } - - void IRAM_ATTR UnipolarMotor::set_direction(bool dir) { - _dir = dir; - } - - void IRAM_ATTR UnipolarMotor::step() { - uint8_t _phase[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // temporary phase values...all start as off - uint8_t phase_max; - - if (!_enabled) { - return; // don't do anything, phase is not changed or lost - } - - phase_max = _half_step ? 7 : 3; - - if (_dir) { // count up - _current_phase = _current_phase == phase_max ? 0 : _current_phase + 1; - } else { // count down - _current_phase = _current_phase == 0 ? phase_max : _current_phase - 1; - } - /* - 8 Step : A – AB – B – BC – C – CD – D – DA - 4 Step : AB – BC – CD – DA - - Step IN4 IN3 IN2 IN1 - A 0 0 0 1 - AB 0 0 1 1 - B 0 0 1 0 - BC 0 1 1 0 - C 0 1 0 0 - CD 1 1 0 0 - D 1 0 0 0 - DA 1 0 0 1 - */ - if (_half_step) { - switch (_current_phase) { - case 0: - _phase[0] = 1; - break; - case 1: - _phase[0] = 1; - _phase[1] = 1; - break; - case 2: - _phase[1] = 1; - break; - case 3: - _phase[1] = 1; - _phase[2] = 1; - break; - case 4: - _phase[2] = 1; - break; - case 5: - _phase[2] = 1; - _phase[3] = 1; - break; - case 6: - _phase[3] = 1; - break; - case 7: - _phase[3] = 1; - _phase[0] = 1; - break; - } - } else { - switch (_current_phase) { - case 0: - _phase[0] = 1; - _phase[1] = 1; - break; - case 1: - _phase[1] = 1; - _phase[2] = 1; - break; - case 2: - _phase[2] = 1; - _phase[3] = 1; - break; - case 3: - _phase[3] = 1; - _phase[0] = 1; - break; - } - } - - _pin_phase0.write(_phase[0]); - _pin_phase1.write(_phase[1]); - _pin_phase2.write(_phase[2]); - _pin_phase3.write(_phase[3]); - Stepping::startPulseTimer(); - } - - // Configuration registration - namespace { - MotorFactory::InstanceBuilder registration("unipolar"); - } -} diff --git a/FluidNC/src/Motors/UnipolarMotor.h b/FluidNC/src/Motors/UnipolarMotor.h deleted file mode 100644 index 872f8054c..000000000 --- a/FluidNC/src/Motors/UnipolarMotor.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2020 - Bart Dring -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#pragma once - -#include "MotorDriver.h" - -namespace MotorDrivers { - class UnipolarMotor : public MotorDriver { - public: - UnipolarMotor(const char* name) : MotorDriver(name) {} - - // Overrides for inherited methods - void init() override; - bool set_homing_mode(bool isHoming) override { return true; } - void set_disable(bool disable) override; - void set_direction(bool) override; - void step() override; - - // Configuration handlers: - void validate() override { - Assert(!_pin_phase0.undefined(), "Phase 0 pin should be configured."); - Assert(!_pin_phase1.undefined(), "Phase 1 pin should be configured."); - Assert(!_pin_phase2.undefined(), "Phase 2 pin should be configured."); - Assert(!_pin_phase3.undefined(), "Phase 3 pin should be configured."); - } - - void group(Configuration::HandlerBase& handler) override { - handler.item("phase0_pin", _pin_phase0); - handler.item("phase1_pin", _pin_phase1); - handler.item("phase2_pin", _pin_phase2); - handler.item("phase3_pin", _pin_phase3); - handler.item("half_step", _half_step); - } - - private: - Pin _pin_phase0; - Pin _pin_phase1; - Pin _pin_phase2; - Pin _pin_phase3; - uint8_t _current_phase = 0; - bool _half_step = true; - bool _enabled = false; - bool _dir = true; - - protected: - void config_message() override; - }; -} diff --git a/FluidNC/src/Pin.h b/FluidNC/src/Pin.h index 9d6cf1d2d..f7d8a7e05 100644 --- a/FluidNC/src/Pin.h +++ b/FluidNC/src/Pin.h @@ -113,6 +113,9 @@ class Pin { Assert(_detail->capabilities().has(expectedBehavior), "Requested pin %s does not have the expected behavior.", name().c_str()); return _detail->_index; } + inline bool canStep() { return _detail->canStep(); } + inline int index() { return _detail->_index; } + inline bool inverted() { return _detail->_inverted; } void write(bool value) const; void synchronousWrite(bool value) const; diff --git a/FluidNC/src/Pins/GPIOPinDetail.cpp b/FluidNC/src/Pins/GPIOPinDetail.cpp index 5a1b201d2..1e40b16b9 100644 --- a/FluidNC/src/Pins/GPIOPinDetail.cpp +++ b/FluidNC/src/Pins/GPIOPinDetail.cpp @@ -83,7 +83,7 @@ namespace Pins { } GPIOPinDetail::GPIOPinDetail(pinnum_t index, PinOptionsParser options) : - PinDetail(index), _capabilities(GetDefaultCapabilities(index)), _attributes(Pins::PinAttributes::Undefined), _readWriteMask(0) { + PinDetail(index), _capabilities(GetDefaultCapabilities(index)), _attributes(Pins::PinAttributes::Undefined) { // NOTE: // // RAII is very important here! If we throw an exception in the constructor, the resources @@ -121,7 +121,7 @@ namespace Pins { _claimed[index] = true; // readWriteMask is xor'ed with the value to invert it if active low - _readWriteMask = int(_attributes.has(PinAttributes::ActiveLow)); + _inverted = _attributes.has(PinAttributes::ActiveLow); } PinAttributes GPIOPinDetail::getAttr() const { @@ -139,13 +139,13 @@ namespace Pins { log_error(toString()); } Assert(_attributes.has(PinAttributes::Output), "Pin %s cannot be written", toString().c_str()); - int value = _readWriteMask ^ high; + int value = _inverted ^ (bool)high; gpio_write(_index, value); } } int IRAM_ATTR GPIOPinDetail::read() { auto raw = gpio_read(_index); - return raw ^ _readWriteMask; + return (bool)raw ^ _inverted; } void GPIOPinDetail::setAttr(PinAttributes value) { @@ -165,7 +165,7 @@ namespace Pins { // If the pin is ActiveLow, we should take that into account here: if (value.has(PinAttributes::Output)) { - gpio_write(_index, int(value.has(PinAttributes::InitialOn)) ^ _readWriteMask); + gpio_write(_index, int(value.has(PinAttributes::InitialOn)) ^ _inverted); } gpio_mode(_index, diff --git a/FluidNC/src/Pins/GPIOPinDetail.h b/FluidNC/src/Pins/GPIOPinDetail.h index 68dbdee0b..d6940b395 100644 --- a/FluidNC/src/Pins/GPIOPinDetail.h +++ b/FluidNC/src/Pins/GPIOPinDetail.h @@ -9,7 +9,6 @@ namespace Pins { class GPIOPinDetail : public PinDetail { PinCapabilities _capabilities; PinAttributes _attributes; - int _readWriteMask; static PinCapabilities GetDefaultCapabilities(pinnum_t index); @@ -32,6 +31,8 @@ namespace Pins { void setAttr(PinAttributes value) override; PinAttributes getAttr() const override; + bool canStep() override { return true; } + void registerEvent(EventPin* obj) override; std::string toString() override; diff --git a/FluidNC/src/Pins/I2SOPinDetail.cpp b/FluidNC/src/Pins/I2SOPinDetail.cpp index f54938131..b6a5f1db5 100644 --- a/FluidNC/src/Pins/I2SOPinDetail.cpp +++ b/FluidNC/src/Pins/I2SOPinDetail.cpp @@ -11,8 +11,7 @@ namespace Pins { std::vector I2SOPinDetail::_claimed(nI2SOPins, false); I2SOPinDetail::I2SOPinDetail(pinnum_t index, const PinOptionsParser& options) : - PinDetail(index), _capabilities(PinCapabilities::Output | PinCapabilities::I2S), _attributes(Pins::PinAttributes::Undefined), - _readWriteMask(0) { + PinDetail(index), _capabilities(PinCapabilities::Output | PinCapabilities::I2S), _attributes(Pins::PinAttributes::Undefined) { Assert(index < nI2SOPins, "Pin number is greater than max %d", nI2SOPins - 1); Assert(!_claimed[index], "Pin is already used."); // User defined pin capabilities @@ -28,7 +27,7 @@ namespace Pins { _claimed[index] = true; // readWriteMask is xor'ed with the value to invert it if active low - _readWriteMask = int(_attributes.has(PinAttributes::ActiveLow)); + _inverted = _attributes.has(PinAttributes::ActiveLow); } PinCapabilities I2SOPinDetail::capabilities() const { @@ -40,7 +39,7 @@ namespace Pins { void IRAM_ATTR I2SOPinDetail::write(int high) { if (high != _lastWrittenValue) { _lastWrittenValue = high; - i2s_out_write(_index, _readWriteMask ^ high); + i2s_out_write(_index, _inverted ^ (bool)high); } } @@ -50,7 +49,7 @@ namespace Pins { if (high != _lastWrittenValue) { _lastWrittenValue = high; - i2s_out_write(_index, _readWriteMask ^ high); + i2s_out_write(_index, _inverted ^ (bool)high); i2s_out_push(); i2s_out_delay(); } @@ -58,7 +57,7 @@ namespace Pins { int I2SOPinDetail::read() { auto raw = i2s_out_read(_index); - return raw ^ _readWriteMask; + return (bool)raw ^ _inverted; } void I2SOPinDetail::setAttr(PinAttributes value) { @@ -77,7 +76,7 @@ namespace Pins { // just check for conflicts above... // If the pin is ActiveLow, we should take that into account here: - i2s_out_write(_index, value.has(PinAttributes::InitialOn) ^ _readWriteMask); + i2s_out_write(_index, value.has(PinAttributes::InitialOn) ^ _inverted); } PinAttributes I2SOPinDetail::getAttr() const { diff --git a/FluidNC/src/Pins/I2SOPinDetail.h b/FluidNC/src/Pins/I2SOPinDetail.h index e47e3d4fe..1f9e1fadd 100644 --- a/FluidNC/src/Pins/I2SOPinDetail.h +++ b/FluidNC/src/Pins/I2SOPinDetail.h @@ -10,7 +10,7 @@ namespace Pins { class I2SOPinDetail : public PinDetail { PinCapabilities _capabilities; PinAttributes _attributes; - int _readWriteMask; + int _inverted; static const int nI2SOPins = 32; static std::vector _claimed; @@ -29,6 +29,8 @@ namespace Pins { void setAttr(PinAttributes value) override; PinAttributes getAttr() const override; + bool canStep() override { return true; } + std::string toString() override; ~I2SOPinDetail() override { _claimed[_index] = false; } diff --git a/FluidNC/src/Pins/PinDetail.h b/FluidNC/src/Pins/PinDetail.h index ecfc77bc7..bec7c89bf 100644 --- a/FluidNC/src/Pins/PinDetail.h +++ b/FluidNC/src/Pins/PinDetail.h @@ -21,13 +21,14 @@ namespace Pins { class PinDetail { protected: public: - int _index; + int _index = -1; + bool _inverted = false; PinDetail(int number) : _index(number) {} - PinDetail(const PinDetail& o) = delete; - PinDetail(PinDetail&& o) = delete; + PinDetail(const PinDetail& o) = delete; + PinDetail(PinDetail&& o) = delete; PinDetail& operator=(const PinDetail& o) = delete; - PinDetail& operator=(PinDetail&& o) = delete; + PinDetail& operator=(PinDetail&& o) = delete; virtual PinCapabilities capabilities() const = 0; @@ -38,6 +39,8 @@ namespace Pins { virtual void setAttr(PinAttributes value) = 0; virtual PinAttributes getAttr() const = 0; + virtual bool canStep() { return false; } + virtual void registerEvent(EventPin* obj); virtual std::string toString() = 0; diff --git a/FluidNC/src/Spindles/Laser.cpp b/FluidNC/src/Spindles/LaserSpindle.cpp similarity index 98% rename from FluidNC/src/Spindles/Laser.cpp rename to FluidNC/src/Spindles/LaserSpindle.cpp index f43246079..a04d34d88 100644 --- a/FluidNC/src/Spindles/Laser.cpp +++ b/FluidNC/src/Spindles/LaserSpindle.cpp @@ -6,7 +6,7 @@ M4 speed vs. power compensation. */ -#include "Laser.h" +#include "LaserSpindle.h" #include "Driver/PwmPin.h" // pwmInit(), etc. #include "../Machine/MachineConfig.h" diff --git a/FluidNC/src/Spindles/Laser.h b/FluidNC/src/Spindles/LaserSpindle.h similarity index 100% rename from FluidNC/src/Spindles/Laser.h rename to FluidNC/src/Spindles/LaserSpindle.h diff --git a/FluidNC/src/Stepper.cpp b/FluidNC/src/Stepper.cpp index 6c44bd6e0..563844052 100644 --- a/FluidNC/src/Stepper.cpp +++ b/FluidNC/src/Stepper.cpp @@ -177,7 +177,7 @@ static st_prep_t prep; // Stepper shutdown void IRAM_ATTR Stepper::stop_stepping() { - config->_axes->unstep(); + Stepping::unstep(); st.step_outbits = 0; } @@ -203,7 +203,7 @@ bool IRAM_ATTR Stepper::pulse_func() { } auto n_axis = Axes::_numberAxis; - config->_axes->step(st.step_outbits, st.dir_outbits); + Stepping::step(st.step_outbits, st.dir_outbits); // If there is no step segment, attempt to pop one from the stepper buffer if (st.exec_segment == NULL) { @@ -266,7 +266,7 @@ bool IRAM_ATTR Stepper::pulse_func() { segment_buffer_tail = segment_buffer_tail >= (Stepping::_segments - 1) ? 0 : segment_buffer_tail + 1; } - config->_axes->unstep(); + Stepping::unstep(); return true; } @@ -294,7 +294,7 @@ void Stepper::go_idle() { // Reset and clear stepper subsystem variables void Stepper::reset() { // Initialize Stepping driver idle state. - config->_stepping->reset(); + Stepping::reset(); go_idle(); diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index 0df329e0a..ebf7a5956 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -4,11 +4,20 @@ #include "Stepper.h" #include "Machine/MachineConfig.h" // config +#include +#include + #include namespace Machine { - int Stepping::_engine = RMT; + // fStepperTimer should be an integer divisor of the bus speed, i.e. of fTimers + const int ticksPerMicrosecond = Stepping::fStepperTimer / 1000000; + + int Stepping::_engine = RMT_ENGINE; + + int Stepping::_n_active_axes = 0; + bool Stepping::_switchedStepper = false; int32_t Stepping::_stepPulseEndTime; size_t Stepping::_segments = 12; @@ -19,10 +28,10 @@ namespace Machine { uint32_t Stepping::_disableDelayUsecs = 0; const EnumItem stepTypes[] = { { Stepping::TIMED, "Timed" }, - { Stepping::RMT, "RMT" }, + { Stepping::RMT_ENGINE, "RMT" }, { Stepping::I2S_STATIC, "I2S_static" }, { Stepping::I2S_STREAM, "I2S_stream" }, - EnumItem(Stepping::RMT) }; + EnumItem(Stepping::RMT_ENGINE) }; void Stepping::init() { log_info("Stepping:" << stepTypes[_engine].name << " Pulse:" << _pulseUsecs << "us Dsbl Delay:" << _disableDelayUsecs @@ -41,6 +50,196 @@ namespace Machine { Stepper::init(); } + rmt_channel_t _rmt_chan_num = RMT_CHANNEL_MAX; + + static int init_rmt_channel(rmt_channel_t& rmt_chan_num, int step_gpio, bool invert_step, uint32_t dir_delay_ms, uint32_t pulse_us) { + static rmt_channel_t next_RMT_chan_num = RMT_CHANNEL_0; + if (rmt_chan_num == RMT_CHANNEL_MAX) { + if (next_RMT_chan_num == RMT_CHANNEL_MAX) { + log_error("Out of RMT channels"); + return -1; + } + rmt_chan_num = next_RMT_chan_num; + next_RMT_chan_num = static_cast(static_cast(next_RMT_chan_num) + 1); + } + + rmt_config_t rmtConfig = { .rmt_mode = RMT_MODE_TX, + .channel = rmt_chan_num, + .gpio_num = gpio_num_t(step_gpio), + .clk_div = 20, + .mem_block_num = 2, + .flags = 0, + .tx_config = { + .carrier_freq_hz = 0, + .carrier_level = RMT_CARRIER_LEVEL_LOW, + .idle_level = invert_step ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW, + .carrier_duty_percent = 50, +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + .loop_count = 1, +#endif + .carrier_en = false, + .loop_en = false, + .idle_output_en = true, + } }; + + rmt_item32_t rmtItem[2]; + rmtItem[0].duration0 = dir_delay_ms ? dir_delay_ms * 4 : 1; + rmtItem[0].duration1 = 4 * pulse_us; + rmtItem[1].duration0 = 0; + rmtItem[1].duration1 = 0; + + rmtItem[0].level0 = rmtConfig.tx_config.idle_level; + rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; + rmt_config(&rmtConfig); + rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); + return rmt_chan_num; + } + + Stepping::motor_t* Stepping::axis_motors[MAX_N_AXIS][MAX_MOTORS_PER_AXIS] = { nullptr }; + + void Stepping::assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert) { + if (axis >= _n_active_axes) { + _n_active_axes = axis + 1; + } + if (_engine == RMT_ENGINE) { + step_pin = init_rmt_channel(_rmt_chan_num, step_pin, step_invert, _directionDelayUsecs, _pulseUsecs); + } + + motor_t* m = new motor_t; + axis_motors[axis][motor] = m; + m->step_pin = step_pin; + m->step_invert = step_invert; + m->dir_pin = dir_pin; + m->dir_invert = dir_invert; + m->blocked = false; + m->limited = false; + } + + int Stepping::axis_steps[MAX_N_AXIS] = { 0 }; + + bool* Stepping::limit_var(int axis, int motor) { + auto m = axis_motors[axis][motor]; + return m ? &(m->limited) : nullptr; + } + + void Stepping::block(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->blocked = true; + } + } + + void Stepping::unblock(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->blocked = false; + } + } + + void Stepping::limit(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->limited = true; + } + } + void Stepping::unlimit(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->limited = false; + } + } + + void IRAM_ATTR Stepping::step(uint8_t step_mask, uint8_t dir_mask) { + // Set the direction pins, but optimize for the common + // situation where the direction bits haven't changed. + static uint8_t previous_dir_mask = 255; // should never be this value + if (dir_mask != previous_dir_mask) { + for (size_t axis = 0; axis < _n_active_axes; axis++) { + bool dir = bitnum_is_true(dir_mask, axis); + bool old_dir = bitnum_is_true(previous_dir_mask, axis); + if (dir != old_dir) { + for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { + auto m = axis_motors[axis][motor]; + if (m) { + int pin = m->dir_pin; + bool inverted = m->dir_invert; + bool direction = dir ^ inverted; + if (_engine == RMT_ENGINE || _engine == TIMED) { + gpio_write(pin, direction); + } else if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + i2s_out_write(pin, direction); + } + } + } + } + waitDirection(); + previous_dir_mask = dir_mask; + } + } + + // Turn on step pulses for motors that are supposed to step now + for (size_t axis = 0; axis < _n_active_axes; axis++) { + if (bitnum_is_true(step_mask, axis)) { + auto increment = bitnum_is_true(dir_mask, axis) ? 1 : -1; + axis_steps[axis] += increment; + for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { + auto m = axis_motors[axis][motor]; + if (m && !m->blocked && !m->limited) { + int pin = m->step_pin; + bool inverted = m->step_invert; + if (_engine == RMT_ENGINE) { + // Restart the RMT which has already been configured + // for the desired pulse length and polarity +#ifdef CONFIG_IDF_TARGET_ESP32 + RMT.conf_ch[pin].conf1.mem_rd_rst = 1; + RMT.conf_ch[pin].conf1.mem_rd_rst = 0; + RMT.conf_ch[pin].conf1.tx_start = 1; +#endif +#ifdef CONFIG_IDF_TARGET_ESP32S3 + RMT.chnconf0[pin].mem_rd_rst_n = 1; + RMT.chnconf0[pin].mem_rd_rst_n = 0; + RMT.chnconf0[pin].tx_start_n = 1; +#endif + } else if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + i2s_out_write(pin, !inverted); + } else if (_engine == TIMED) { + gpio_write(pin, !inverted); + } + } + } + } + } + startPulseTimer(); + } + + // Turn all stepper pins off + void IRAM_ATTR Stepping::unstep() { + // With RMT, the end of the step is automatic + if (_engine == RMT_ENGINE) { + return; + } + if (_engine == I2S_STATIC || _engine == TIMED) { // Wait pulse + spinUntil(_stepPulseEndTime); + } + for (size_t axis = 0; axis < _n_active_axes; axis++) { + for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { + auto m = axis_motors[axis][motor]; + if (m) { + int pin = m->step_pin; + bool inverted = m->step_invert; + if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + i2s_out_write(pin, inverted); + } else if (_engine == TIMED) { + gpio_write(pin, inverted); + } + } + } + } + if (_engine == stepper_id_t::I2S_STATIC) { + i2s_out_push(); + } + } + void Stepping::reset() { if (_engine == I2S_STREAM) { i2s_out_reset(); @@ -65,14 +264,8 @@ namespace Machine { _engine = I2S_STREAM; } } - // Called only from Axes::unstep() - void IRAM_ATTR Stepping::waitPulse() { - if (_engine == I2S_STATIC || _engine == TIMED) { - spinUntil(_stepPulseEndTime); - } - } - // Called only from Axes::step() + // Called only from step() void IRAM_ATTR Stepping::waitDirection() { if (_directionDelayUsecs) { // Stepper drivers need some time between changing direction and doing a pulse. @@ -91,7 +284,7 @@ namespace Machine { } } - // Called from Axes::step() and, probably incorrectly, from UnipolarMotor::step() + // Called from step() void IRAM_ATTR Stepping::startPulseTimer() { // Do not use switch() in IRAM if (_engine == stepper_id_t::I2S_STREAM) { @@ -105,13 +298,6 @@ namespace Machine { } } - // Called only from Axes::unstep() - void IRAM_ATTR Stepping::finishPulse() { - if (_engine == stepper_id_t::I2S_STATIC) { - i2s_out_push(); - } - } - // Called only from Stepper::pulse_func when a new segment is loaded // The argument is in units of ticks of the timer that generates ISRs void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { @@ -169,7 +355,7 @@ namespace Machine { case stepper_id_t::I2S_STREAM: case stepper_id_t::I2S_STATIC: return i2s_out_max_steps_per_sec; - case stepper_id_t::RMT: + case stepper_id_t::RMT_ENGINE: return 1000000 / (2 * _pulseUsecs + _directionDelayUsecs); case stepper_id_t::TIMED: default: diff --git a/FluidNC/src/Stepping.h b/FluidNC/src/Stepping.h index 0dfb25f6f..466a4c549 100644 --- a/FluidNC/src/Stepping.h +++ b/FluidNC/src/Stepping.h @@ -6,24 +6,35 @@ #include "Configuration/Configurable.h" #include "Driver/StepTimer.h" - namespace Machine { class Stepping : public Configuration::Configurable { public: // fStepperTimer should be an integer divisor of the bus speed, i.e. of fTimers static const uint32_t fStepperTimer = 20000000; // frequency of step pulse timer - private: - static const int ticksPerMicrosecond = fStepperTimer / 1000000; - static bool _switchedStepper; static int32_t _stepPulseEndTime; + static const int MAX_MOTORS_PER_AXIS = 2; + struct motor_t { + int step_pin; + int dir_pin; + bool step_invert; + bool dir_invert; + bool blocked; + bool limited; + }; + static motor_t* axis_motors[MAX_N_AXIS][MAX_MOTORS_PER_AXIS]; + static int _n_active_axes; + + static void startPulseTimer(); + static void waitDirection(); // Wait for direction delay + static int32_t axis_steps[MAX_N_AXIS]; public: enum stepper_id_t { TIMED = 0, - RMT, + RMT_ENGINE, I2S_STATIC, I2S_STREAM, }; @@ -49,20 +60,26 @@ namespace Machine { // Interfaces to stepping engine static void init(); - static void assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert); + static uint32_t getSteps(int axis) { return axis_steps[axis]; } + static void setSteps(int axis, uint32_t steps) { axis_steps[axis] = steps; } - static void setStepPin(int axis, int motor, int pin, bool invert); - static void setDirPin(int axis, int motor, int pin, bool invert); + static void assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert); static void reset(); // Clean up old state and start fresh static void beginLowLatency(); static void endLowLatency(); - static void startPulseTimer(); - static void waitPulse(); // Wait for pulse length - static void waitDirection(); // Wait for direction delay - static void waitMotion(); // Wait for motion to complete - static void finishPulse(); // Cleanup after unstep + static void step(uint8_t step_mask, uint8_t dir_mask); + static void unstep(); + + // Used to stop a motor quickly when a limit switch is hit + static bool* limit_var(int axis, int motor); + static void limit(int axis, int motor); + static void unlimit(int axis, int motor); + + // Used to stop a motor during ganged homint + static void block(int axis, int motor); + static void unblock(int axis, int motor); static uint32_t maxPulsesPerSec(); diff --git a/FluidNC/src/System.cpp b/FluidNC/src/System.cpp index 907f48e93..dd07eb4c0 100644 --- a/FluidNC/src/System.cpp +++ b/FluidNC/src/System.cpp @@ -10,6 +10,7 @@ #include "Report.h" // report_ovr_counter #include "Config.h" // MAX_N_AXIS #include "Machine/MachineConfig.h" // config +#include "src/Stepping.h" // config #include // memset #include // roundf @@ -51,17 +52,11 @@ void motor_steps_to_mpos(float* position, int32_t* steps) { } void set_motor_steps(size_t axis, int32_t steps) { - auto a = config->_axes->_axis[axis]; - for (size_t motor = 0; motor < Machine::Axis::MAX_MOTORS_PER_AXIS; motor++) { - auto m = a->_motors[motor]; - if (m) { - m->_steps = steps; - } - } + Stepping::setSteps(axis, steps); } void set_motor_steps_from_mpos(float* mpos) { - auto n_axis = config->_axes->_numberAxis; + auto n_axis = Axes::_numberAxis; float motor_steps[n_axis]; config->_kinematics->transform_cartesian_to_motors(motor_steps, mpos); for (size_t axis = 0; axis < n_axis; axis++) { @@ -70,16 +65,14 @@ void set_motor_steps_from_mpos(float* mpos) { } int32_t get_axis_motor_steps(size_t axis) { - auto m = config->_axes->_axis[axis]->_motors[0]; - return m ? m->_steps : 0; + return Stepping::getSteps(axis); } void get_motor_steps(int32_t* motor_steps) { auto axes = config->_axes; auto n_axis = axes->_numberAxis; for (size_t axis = 0; axis < n_axis; axis++) { - auto m = axes->_axis[axis]->_motors[0]; - motor_steps[axis] = m ? m->_steps : 0; + motor_steps[axis] = Stepping::getSteps(axis); } } int32_t* get_motor_steps() { From db2132ecee7ce4c23fef93ff43ac4ed7dd46d0ba Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Fri, 4 Oct 2024 21:08:58 -1000 Subject: [PATCH 03/30] Fixed RMT problem --- FluidNC/src/Machine/LimitPin.cpp | 10 ++++++---- FluidNC/src/Motors/TrinamicBase.cpp | 1 + FluidNC/src/Stepping.cpp | 22 ++++++++++------------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/FluidNC/src/Machine/LimitPin.cpp b/FluidNC/src/Machine/LimitPin.cpp index ccc3adeec..7331489ce 100644 --- a/FluidNC/src/Machine/LimitPin.cpp +++ b/FluidNC/src/Machine/LimitPin.cpp @@ -56,8 +56,9 @@ namespace Machine { void LimitPin::update(bool value) { if (value) { if (Homing::approach() || (!state_is(State::Homing) && _pHardLimits)) { - *_pLimited = value; - + if (_pLimited != nullptr) { + *_pLimited = value; + } if (_pExtraLimited != nullptr) { *_pExtraLimited = value; } @@ -70,8 +71,9 @@ namespace Machine { set_bits(*_negLimits, _bitmask); } } else { - *_pLimited = value; - + if (_pLimited != nullptr) { + *_pLimited = value; + } if (_pExtraLimited != nullptr) { *_pExtraLimited = value; } diff --git a/FluidNC/src/Motors/TrinamicBase.cpp b/FluidNC/src/Motors/TrinamicBase.cpp index 502ba859c..f68b29972 100644 --- a/FluidNC/src/Motors/TrinamicBase.cpp +++ b/FluidNC/src/Motors/TrinamicBase.cpp @@ -140,6 +140,7 @@ namespace MotorDrivers { void TrinamicBase::config_motor() { _has_errors = !test(); // Try communicating with motor. Prints an error if there is a problem. + log_debug("Trinamic base config_motor"); init_step_dir_pins(); if (_has_errors) { diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index ebf7a5956..f0a5685f2 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -50,18 +50,14 @@ namespace Machine { Stepper::init(); } - rmt_channel_t _rmt_chan_num = RMT_CHANNEL_MAX; - - static int init_rmt_channel(rmt_channel_t& rmt_chan_num, int step_gpio, bool invert_step, uint32_t dir_delay_ms, uint32_t pulse_us) { + static int init_rmt_channel(int step_gpio, bool invert_step, uint32_t dir_delay_ms, uint32_t pulse_us) { static rmt_channel_t next_RMT_chan_num = RMT_CHANNEL_0; - if (rmt_chan_num == RMT_CHANNEL_MAX) { - if (next_RMT_chan_num == RMT_CHANNEL_MAX) { - log_error("Out of RMT channels"); - return -1; - } - rmt_chan_num = next_RMT_chan_num; - next_RMT_chan_num = static_cast(static_cast(next_RMT_chan_num) + 1); + if (next_RMT_chan_num == RMT_CHANNEL_MAX) { + log_error("Out of RMT channels"); + return -1; } + rmt_channel_t rmt_chan_num = next_RMT_chan_num; + next_RMT_chan_num = static_cast(static_cast(next_RMT_chan_num) + 1); rmt_config_t rmtConfig = { .rmt_mode = RMT_MODE_TX, .channel = rmt_chan_num, @@ -92,17 +88,19 @@ namespace Machine { rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; rmt_config(&rmtConfig); rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); - return rmt_chan_num; + return static_cast(rmt_chan_num); } Stepping::motor_t* Stepping::axis_motors[MAX_N_AXIS][MAX_MOTORS_PER_AXIS] = { nullptr }; void Stepping::assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert) { + log_debug("assignMotor " << axis << " " << motor << " " << step_pin << " " << dir_pin << " " << dir_invert); if (axis >= _n_active_axes) { _n_active_axes = axis + 1; } if (_engine == RMT_ENGINE) { - step_pin = init_rmt_channel(_rmt_chan_num, step_pin, step_invert, _directionDelayUsecs, _pulseUsecs); + step_pin = init_rmt_channel(step_pin, step_invert, _directionDelayUsecs, _pulseUsecs); + log_debug("RMT num " << step_pin << " for axis " << axis << " motor " << motor); } motor_t* m = new motor_t; From 10ce701188b934c739342dc4e2f7daa9655cd3b8 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sat, 5 Oct 2024 04:57:14 -1000 Subject: [PATCH 04/30] Fixed limit and homing init order problems --- FluidNC/src/Machine/LimitPin.cpp | 3 ++- FluidNC/src/Motors/TrinamicBase.cpp | 7 ++++--- FluidNC/src/Motors/TrinamicBase.h | 1 + FluidNC/src/Motors/TrinamicSpiDriver.cpp | 4 +++- FluidNC/src/Motors/TrinamicUartDriver.cpp | 1 + FluidNC/src/Stepping.cpp | 12 +++++++----- 6 files changed, 18 insertions(+), 10 deletions(-) diff --git a/FluidNC/src/Machine/LimitPin.cpp b/FluidNC/src/Machine/LimitPin.cpp index 7331489ce..72addefc6 100644 --- a/FluidNC/src/Machine/LimitPin.cpp +++ b/FluidNC/src/Machine/LimitPin.cpp @@ -8,7 +8,6 @@ namespace Machine { LimitPin::LimitPin(Pin& pin, int axis, int motor, int direction, bool& pHardLimits) : EventPin(&limitEvent, "Limit"), _axis(axis), _motorNum(motor), _value(false), _pHardLimits(pHardLimits), _pin(&pin) { - _pLimited = Stepping::limit_var(axis, motor); const char* sDir; // Select one or two bitmask variables to receive the switch data switch (direction) { @@ -47,6 +46,8 @@ namespace Machine { if (_pin->undefined()) { return; } + _pLimited = Stepping::limit_var(_axis, _motorNum); + _pin->report(_legend); _pin->setAttr(Pin::Attr::Input); _pin->registerEvent(static_cast(this)); diff --git a/FluidNC/src/Motors/TrinamicBase.cpp b/FluidNC/src/Motors/TrinamicBase.cpp index f68b29972..21ca867e9 100644 --- a/FluidNC/src/Motors/TrinamicBase.cpp +++ b/FluidNC/src/Motors/TrinamicBase.cpp @@ -137,12 +137,13 @@ namespace MotorDrivers { return true; } + void TrinamicBase::init() { + init_step_dir_pins(); + } + void TrinamicBase::config_motor() { _has_errors = !test(); // Try communicating with motor. Prints an error if there is a problem. - log_debug("Trinamic base config_motor"); - init_step_dir_pins(); - if (_has_errors) { return; } diff --git a/FluidNC/src/Motors/TrinamicBase.h b/FluidNC/src/Motors/TrinamicBase.h index 599ab8cd4..2832082eb 100644 --- a/FluidNC/src/Motors/TrinamicBase.h +++ b/FluidNC/src/Motors/TrinamicBase.h @@ -63,6 +63,7 @@ namespace MotorDrivers { void reportCommsFailure(void); bool checkVersion(uint8_t expected, uint8_t got); bool startDisable(bool disable); + void init() override; virtual void config_motor(); const char* yn(bool v) { return v ? "Y" : "N"; } diff --git a/FluidNC/src/Motors/TrinamicSpiDriver.cpp b/FluidNC/src/Motors/TrinamicSpiDriver.cpp index 15d995b2c..4769fe743 100644 --- a/FluidNC/src/Motors/TrinamicSpiDriver.cpp +++ b/FluidNC/src/Motors/TrinamicSpiDriver.cpp @@ -15,7 +15,9 @@ namespace MotorDrivers { pinnum_t TrinamicSpiDriver::daisy_chain_cs_id = 255; uint8_t TrinamicSpiDriver::spi_index_mask = 0; - void TrinamicSpiDriver::init() {} + void TrinamicSpiDriver::init() { + TrinamicBase::init(); + } uint8_t TrinamicSpiDriver::setupSPI() { _has_errors = false; diff --git a/FluidNC/src/Motors/TrinamicUartDriver.cpp b/FluidNC/src/Motors/TrinamicUartDriver.cpp index 97ac24729..f5009fa71 100644 --- a/FluidNC/src/Motors/TrinamicUartDriver.cpp +++ b/FluidNC/src/Motors/TrinamicUartDriver.cpp @@ -24,6 +24,7 @@ namespace MotorDrivers { Assert(_uart, "TMC Driver missing uart%d section", _uart_num); _cs_pin.setAttr(Pin::Attr::Output); + TrinamicBase::init(); } /* diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index f0a5685f2..bfbd9d83a 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -94,13 +94,11 @@ namespace Machine { Stepping::motor_t* Stepping::axis_motors[MAX_N_AXIS][MAX_MOTORS_PER_AXIS] = { nullptr }; void Stepping::assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert) { - log_debug("assignMotor " << axis << " " << motor << " " << step_pin << " " << dir_pin << " " << dir_invert); if (axis >= _n_active_axes) { _n_active_axes = axis + 1; } if (_engine == RMT_ENGINE) { step_pin = init_rmt_channel(step_pin, step_invert, _directionDelayUsecs, _pulseUsecs); - log_debug("RMT num " << step_pin << " for axis " << axis << " motor " << motor); } motor_t* m = new motor_t; @@ -151,6 +149,11 @@ namespace Machine { // Set the direction pins, but optimize for the common // situation where the direction bits haven't changed. static uint8_t previous_dir_mask = 255; // should never be this value + if (previous_dir_mask == 255) { + // Set all the direction bits the first time + previous_dir_mask = ~dir_mask; + } + if (dir_mask != previous_dir_mask) { for (size_t axis = 0; axis < _n_active_axes; axis++) { bool dir = bitnum_is_true(dir_mask, axis); @@ -160,8 +163,7 @@ namespace Machine { auto m = axis_motors[axis][motor]; if (m) { int pin = m->dir_pin; - bool inverted = m->dir_invert; - bool direction = dir ^ inverted; + bool direction = dir ^ m->dir_invert; if (_engine == RMT_ENGINE || _engine == TIMED) { gpio_write(pin, direction); } else if (_engine == I2S_STATIC || _engine == I2S_STREAM) { @@ -171,8 +173,8 @@ namespace Machine { } } waitDirection(); - previous_dir_mask = dir_mask; } + previous_dir_mask = dir_mask; } // Turn on step pulses for motors that are supposed to step now From a26bbe1e4435b4af442ec63399f46b11e9bd9cf6 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sat, 5 Oct 2024 04:57:52 -1000 Subject: [PATCH 05/30] Removed old/unused read_settings() interface --- FluidNC/src/Motors/Dynamixel2.cpp | 2 -- FluidNC/src/Motors/Dynamixel2.h | 1 - FluidNC/src/Motors/MotorDriver.h | 5 ----- FluidNC/src/Motors/RcServo.h | 3 ++- FluidNC/src/Motors/StandardStepper.cpp | 6 ------ FluidNC/src/Motors/StandardStepper.h | 1 - 6 files changed, 2 insertions(+), 16 deletions(-) diff --git a/FluidNC/src/Motors/Dynamixel2.cpp b/FluidNC/src/Motors/Dynamixel2.cpp index 33ffb77f1..769bbbecf 100644 --- a/FluidNC/src/Motors/Dynamixel2.cpp +++ b/FluidNC/src/Motors/Dynamixel2.cpp @@ -106,8 +106,6 @@ namespace MotorDrivers { return true; } - void Dynamixel2::read_settings() {} - // sets the PWM to zero. This allows most servos to be manually moved void IRAM_ATTR Dynamixel2::set_disable(bool disable) { if (_disabled == disable) { diff --git a/FluidNC/src/Motors/Dynamixel2.h b/FluidNC/src/Motors/Dynamixel2.h index 0a2b05b83..a45ba37e1 100644 --- a/FluidNC/src/Motors/Dynamixel2.h +++ b/FluidNC/src/Motors/Dynamixel2.h @@ -107,7 +107,6 @@ namespace MotorDrivers { // Overrides for inherited methods void init() override; - void read_settings() override; bool set_homing_mode(bool isHoming) override; void set_disable(bool disable) override; void update() override; diff --git a/FluidNC/src/Motors/MotorDriver.h b/FluidNC/src/Motors/MotorDriver.h index c62e03c24..2233e6077 100644 --- a/FluidNC/src/Motors/MotorDriver.h +++ b/FluidNC/src/Motors/MotorDriver.h @@ -48,11 +48,6 @@ namespace MotorDrivers { // the stallguard debugging problem. virtual void debug_message(); - // read_settings(), called from init(), re-establishes the motor - // setup from configurable arameters. - // TODO Architecture: Maybe this should be subsumed by init() - virtual void read_settings() {} - // set_homing_mode() is called from motors_set_homing_mode(), // which in turn is called at the beginning of a homing cycle // with isHoming true, and at the end with isHoming false. diff --git a/FluidNC/src/Motors/RcServo.h b/FluidNC/src/Motors/RcServo.h index 37aca7ca4..fb812f41a 100644 --- a/FluidNC/src/Motors/RcServo.h +++ b/FluidNC/src/Motors/RcServo.h @@ -41,9 +41,10 @@ namespace MotorDrivers { } } + void read_settings(); + // Overrides for inherited methods void init() override; - void read_settings() override; bool set_homing_mode(bool isHoming) override; void set_disable(bool disable) override; void update() override; diff --git a/FluidNC/src/Motors/StandardStepper.cpp b/FluidNC/src/Motors/StandardStepper.cpp index e66c9c961..520bcf989 100644 --- a/FluidNC/src/Motors/StandardStepper.cpp +++ b/FluidNC/src/Motors/StandardStepper.cpp @@ -19,11 +19,7 @@ using namespace Machine; namespace MotorDrivers { void StandardStepper::init() { - read_settings(); config_message(); - } - - void StandardStepper::read_settings() { init_step_dir_pins(); } @@ -41,8 +37,6 @@ namespace MotorDrivers { if (_step_pin.canStep()) { Stepping::assignMotor(axisIndex, dualAxisIndex, _step_pin.index(), _step_pin.inverted(), _dir_pin.index(), _dir_pin.inverted()); } - - auto dir_gpio = _step_pin.getNative(Pin::Capabilities::Output); } void StandardStepper::config_message() { diff --git a/FluidNC/src/Motors/StandardStepper.h b/FluidNC/src/Motors/StandardStepper.h index fb060f7ef..3f1e594d5 100644 --- a/FluidNC/src/Motors/StandardStepper.h +++ b/FluidNC/src/Motors/StandardStepper.h @@ -15,7 +15,6 @@ namespace MotorDrivers { // No special action, but return true to say homing is possible bool set_homing_mode(bool isHoming) override { return true; } void set_disable(bool) override; - void read_settings() override; void init_step_dir_pins(); From a53fc04b8491a8343b55a09f14aa019bfd8bd8dc Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 6 Oct 2024 05:37:58 -1000 Subject: [PATCH 06/30] Fixed inverted position reports --- FluidNC/src/Stepping.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index bfbd9d83a..0c2bc35bd 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -180,7 +180,7 @@ namespace Machine { // Turn on step pulses for motors that are supposed to step now for (size_t axis = 0; axis < _n_active_axes; axis++) { if (bitnum_is_true(step_mask, axis)) { - auto increment = bitnum_is_true(dir_mask, axis) ? 1 : -1; + auto increment = bitnum_is_true(dir_mask, axis) ? -1 : 1; axis_steps[axis] += increment; for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { auto m = axis_motors[axis][motor]; From c283f80604f5b07f7d6a493e028667ffbdebcdb3 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 6 Oct 2024 05:43:22 -1000 Subject: [PATCH 07/30] I2S FIFO --- FluidNC/src/I2SOut.cpp | 33 ++++++++++++++++++++------- FluidNC/src/I2SOut.h | 2 ++ FluidNC/src/Stepping.cpp | 48 +++++++++++++++++++++++++--------------- FluidNC/src/Stepping.h | 1 + 4 files changed, 58 insertions(+), 26 deletions(-) diff --git a/FluidNC/src/I2SOut.cpp b/FluidNC/src/I2SOut.cpp index d3536edf7..f1da3cf05 100644 --- a/FluidNC/src/I2SOut.cpp +++ b/FluidNC/src/I2SOut.cpp @@ -180,6 +180,10 @@ void IRAM_ATTR i2s_out_push() { set_single_data(ATOMIC_LOAD(&i2s_out_port_data)); } } +void IRAM_ATTR i2s_out_push_fifo() { + uint32_t portData = ATOMIC_LOAD(&i2s_out_port_data); + I2S0.fifo_wr = portData << DATA_SHIFT; +} static inline void i2s_out_reset_fifo_without_lock() { I2S0.conf.rx_fifo_reset = 1; @@ -291,7 +295,7 @@ static int i2s_out_start() { // Attach I2S to specified GPIO pin i2s_out_gpio_attach(i2s_out_ws_pin, i2s_out_bck_pin, i2s_out_data_pin); - // reest TX/RX module + // reset TX/RX module I2S0.conf.tx_reset = 1; I2S0.conf.tx_reset = 0; I2S0.conf.rx_reset = 1; @@ -310,8 +314,12 @@ static int i2s_out_start() { // start DMA link if (i2s_out_pulser_status == PASSTHROUGH) { +# if 0 I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf_single_data = port_data; +# else + I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) +# endif + I2S0.conf_single_data = port_data; } else { I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) I2S0.conf_single_data = 0; @@ -320,7 +328,11 @@ static int i2s_out_start() { I2S0.conf1.tx_stop_en = 1; // BCK and WCK are suppressed while FIFO is empty // Connect DMA to FIFO +# if 0 I2S0.fifo_conf.dscr_en = 1; // Set this bit to enable I2S DMA mode. (R/W) +# else + I2S0.fifo_conf.dscr_en = 0; // Set this bit to enable I2S DMA mode. (R/W) +# endif I2S0.int_clr.val = 0xFFFFFFFF; I2S0.out_link.start = 1; @@ -775,8 +787,9 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.lc_conf.out_no_restart_clr = 0; I2S0.lc_conf.indscr_burst_en = 0; I2S0.lc_conf.out_eof_mode = 1; // I2S_OUT_EOF_INT generated when DMA has popped all data from the FIFO; - I2S0.conf2.lcd_en = 0; - I2S0.conf2.camera_en = 0; + + I2S0.conf2.lcd_en = 0; + I2S0.conf2.camera_en = 0; # ifdef SOC_I2S_SUPPORTS_PDM_TX // i2s_ll_tx_enable_pdm(dev, false); // i2s_ll_tx_enable_pdm(dev2, false); @@ -802,8 +815,8 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.sample_rate_conf.tx_bits_mod = 16; // default is 16-bits I2S0.sample_rate_conf.rx_bits_mod = 16; // default is 16-bits # else - I2S0.fifo_conf.tx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.fifo_conf.rx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data + I2S0.fifo_conf.tx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data + I2S0.fifo_conf.rx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data // Data width is 32-bit. Forgetting this setting will result in a 16-bit transfer. I2S0.sample_rate_conf.tx_bits_mod = 32; I2S0.sample_rate_conf.rx_bits_mod = 32; @@ -813,9 +826,13 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.conf_chan.rx_chan_mod = 1; // 1: right+right I2S0.conf.rx_mono = 0; +# if 0 I2S0.fifo_conf.dscr_en = 1; //connect DMA to fifo - I2S0.conf.tx_start = 0; - I2S0.conf.rx_start = 0; +# else + I2S0.fifo_conf.dscr_en = 0; //connect DMA to fifo +# endif + I2S0.conf.tx_start = 0; + I2S0.conf.rx_start = 0; I2S0.conf.tx_msb_right = 1; // Set this bit to place right-channel data at the MSB in the transmit FIFO. I2S0.conf.tx_right_first = 0; // Setting this bit allows the right-channel data to be sent first. diff --git a/FluidNC/src/I2SOut.h b/FluidNC/src/I2SOut.h index b02e39588..4caad6618 100644 --- a/FluidNC/src/I2SOut.h +++ b/FluidNC/src/I2SOut.h @@ -86,6 +86,8 @@ uint8_t i2s_out_read(pinnum_t pin); void i2s_out_push(); +void i2s_out_push_fifo(); + /* Set a bit in the internal pin state var. (not written electrically) pin: expanded pin No. (0..31) diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index 0c2bc35bd..dc14ac99e 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -22,6 +22,8 @@ namespace Machine { int32_t Stepping::_stepPulseEndTime; size_t Stepping::_segments = 12; + int Stepping::_i2sPulseCounts; + uint32_t Stepping::_idleMsecs = 255; uint32_t Stepping::_pulseUsecs = 4; uint32_t Stepping::_directionDelayUsecs = 0; @@ -34,8 +36,14 @@ namespace Machine { EnumItem(Stepping::RMT_ENGINE) }; void Stepping::init() { + if (_engine == I2S_STATIC) { + // Number of I2S frames for a pulse, rounded up + _i2sPulseCounts = (_pulseUsecs + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; + } + log_info("Stepping:" << stepTypes[_engine].name << " Pulse:" << _pulseUsecs << "us Dsbl Delay:" << _disableDelayUsecs - << "us Dir Delay:" << _directionDelayUsecs << "us Idle Delay:" << _idleMsecs << "ms"); + << "us Dir Delay:" << _directionDelayUsecs << "us Idle Delay:" << _idleMsecs << "ms" + << " Pulses: " << _i2sPulseCounts); // Prepare stepping interrupt callbacks. The one that is actually // used is determined by timerStart() and timerStop() @@ -209,7 +217,21 @@ namespace Machine { } } } - startPulseTimer(); + // Do not use switch() in IRAM + if (_engine == stepper_id_t::I2S_STREAM) { + // Generate the number of pulses needed to span pulse_microseconds + i2s_out_push_sample(_pulseUsecs); + } else if (_engine == stepper_id_t::I2S_STATIC) { + // i2s_out_push(); + for (int i = 0; i < _i2sPulseCounts; i++) { + i2s_out_push_fifo(); + } +#if 0 + _stepPulseEndTime = usToEndTicks(_pulseUsecs); +#endif + } else if (_engine == stepper_id_t::TIMED) { + _stepPulseEndTime = usToEndTicks(_pulseUsecs); + } } // Turn all stepper pins off @@ -218,9 +240,11 @@ namespace Machine { if (_engine == RMT_ENGINE) { return; } +#if 0 if (_engine == I2S_STATIC || _engine == TIMED) { // Wait pulse spinUntil(_stepPulseEndTime); } +#endif for (size_t axis = 0; axis < _n_active_axes; axis++) { for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { auto m = axis_motors[axis][motor]; @@ -236,7 +260,8 @@ namespace Machine { } } if (_engine == stepper_id_t::I2S_STATIC) { - i2s_out_push(); + // i2s_out_push(); + i2s_out_push_fifo(); } } @@ -275,7 +300,8 @@ namespace Machine { i2s_out_push_sample(_directionDelayUsecs); } else if (_engine == stepper_id_t::I2S_STATIC) { // Commit the pin changes to the hardware immediately - i2s_out_push(); + // i2s_out_push(); + i2s_out_push_fifo(); delay_us(_directionDelayUsecs); } else if (_engine == stepper_id_t::TIMED) { // If we are using RMT, we can't delay here. @@ -284,20 +310,6 @@ namespace Machine { } } - // Called from step() - void IRAM_ATTR Stepping::startPulseTimer() { - // Do not use switch() in IRAM - if (_engine == stepper_id_t::I2S_STREAM) { - // Generate the number of pulses needed to span pulse_microseconds - i2s_out_push_sample(_pulseUsecs); - } else if (_engine == stepper_id_t::I2S_STATIC) { - i2s_out_push(); - _stepPulseEndTime = usToEndTicks(_pulseUsecs); - } else if (_engine == stepper_id_t::TIMED) { - _stepPulseEndTime = usToEndTicks(_pulseUsecs); - } - } - // Called only from Stepper::pulse_func when a new segment is loaded // The argument is in units of ticks of the timer that generates ISRs void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { diff --git a/FluidNC/src/Stepping.h b/FluidNC/src/Stepping.h index 466a4c549..a60ed81c2 100644 --- a/FluidNC/src/Stepping.h +++ b/FluidNC/src/Stepping.h @@ -14,6 +14,7 @@ namespace Machine { private: static bool _switchedStepper; static int32_t _stepPulseEndTime; + static int _i2sPulseCounts; static const int MAX_MOTORS_PER_AXIS = 2; struct motor_t { From 38bca56f13805877b06a83b32f2bd042323deac4 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 6 Oct 2024 09:21:05 -1000 Subject: [PATCH 08/30] Limit max speed according to pulse length --- FluidNC/src/Machine/Axis.cpp | 2 +- FluidNC/src/Stepping.cpp | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/FluidNC/src/Machine/Axis.cpp b/FluidNC/src/Machine/Axis.cpp index d417a00a9..e66ed5c32 100644 --- a/FluidNC/src/Machine/Axis.cpp +++ b/FluidNC/src/Machine/Axis.cpp @@ -26,7 +26,7 @@ namespace Machine { void Axis::afterParse() { uint32_t stepRate = uint32_t(_stepsPerMm * _maxRate / 60.0); - auto maxRate = config->_stepping->maxPulsesPerSec(); + auto maxRate = Stepping::maxPulsesPerSec(); Assert(stepRate <= maxRate, "Stepping rate %d steps/sec exceeds the maximum rate %d", stepRate, maxRate); if (_motors[0] == nullptr) { _motors[0] = new Machine::Motor(_axis, 0); diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index dc14ac99e..cab983f0d 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -22,7 +22,7 @@ namespace Machine { int32_t Stepping::_stepPulseEndTime; size_t Stepping::_segments = 12; - int Stepping::_i2sPulseCounts; + int Stepping::_i2sPulseCounts = 2; uint32_t Stepping::_idleMsecs = 255; uint32_t Stepping::_pulseUsecs = 4; @@ -36,11 +36,6 @@ namespace Machine { EnumItem(Stepping::RMT_ENGINE) }; void Stepping::init() { - if (_engine == I2S_STATIC) { - // Number of I2S frames for a pulse, rounded up - _i2sPulseCounts = (_pulseUsecs + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; - } - log_info("Stepping:" << stepTypes[_engine].name << " Pulse:" << _pulseUsecs << "us Dsbl Delay:" << _disableDelayUsecs << "us Dir Delay:" << _directionDelayUsecs << "us Idle Delay:" << _idleMsecs << "ms" << " Pulses: " << _i2sPulseCounts); @@ -355,18 +350,22 @@ namespace Machine { log_warn("Increasing stepping/pulse_us to the IS2 minimum value " << I2S_OUT_USEC_PER_PULSE); _pulseUsecs = I2S_OUT_USEC_PER_PULSE; } - if (_engine == I2S_STREAM && _pulseUsecs > I2S_STREAM_MAX_USEC_PER_PULSE) { + if ((_engine == I2S_STATIC || _engine == I2S_STREAM) && _pulseUsecs > I2S_STREAM_MAX_USEC_PER_PULSE) { log_warn("Decreasing stepping/pulse_us to " << I2S_STREAM_MAX_USEC_PER_PULSE << ", the maximum value for I2S_STREAM"); _pulseUsecs = I2S_STREAM_MAX_USEC_PER_PULSE; } } + if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + // Number of I2S frames for a pulse, rounded up + _i2sPulseCounts = (_pulseUsecs + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; + } } uint32_t Stepping::maxPulsesPerSec() { switch (_engine) { case stepper_id_t::I2S_STREAM: case stepper_id_t::I2S_STATIC: - return i2s_out_max_steps_per_sec; + return 1000000 / ((_i2sPulseCounts + 1) * I2S_OUT_USEC_PER_PULSE); case stepper_id_t::RMT_ENGINE: return 1000000 / (2 * _pulseUsecs + _directionDelayUsecs); case stepper_id_t::TIMED: From 6f10620f50a441a01949ac1a863d6ee06d7db41b Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 6 Oct 2024 09:26:34 -1000 Subject: [PATCH 09/30] Make pulse_us apply to I2S pulse inactive time too --- FluidNC/src/Stepping.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index cab983f0d..ecfd4151b 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -255,8 +255,9 @@ namespace Machine { } } if (_engine == stepper_id_t::I2S_STATIC) { - // i2s_out_push(); - i2s_out_push_fifo(); + for (int i = 0; i < _i2sPulseCounts; i++) { + i2s_out_push_fifo(); + } } } @@ -365,7 +366,7 @@ namespace Machine { switch (_engine) { case stepper_id_t::I2S_STREAM: case stepper_id_t::I2S_STATIC: - return 1000000 / ((_i2sPulseCounts + 1) * I2S_OUT_USEC_PER_PULSE); + return 1000000 / ((2 * _i2sPulseCounts) * I2S_OUT_USEC_PER_PULSE); case stepper_id_t::RMT_ENGINE: return 1000000 / (2 * _pulseUsecs + _directionDelayUsecs); case stepper_id_t::TIMED: From 13b20a1097699e4fcf80f07ca1e815b19f48e2bf Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 6 Oct 2024 10:02:19 -1000 Subject: [PATCH 10/30] Cleanup unused functions, add count argument to i2s_push_fifo() --- FluidNC/src/I2SOut.cpp | 543 +---------------------------- FluidNC/src/I2SOut.h | 73 +--- FluidNC/src/MotionControl.cpp | 1 - FluidNC/src/Pins/I2SOPinDetail.cpp | 2 +- FluidNC/src/Stepping.cpp | 111 ++---- 5 files changed, 62 insertions(+), 668 deletions(-) diff --git a/FluidNC/src/I2SOut.cpp b/FluidNC/src/I2SOut.cpp index f1da3cf05..10fdd5abb 100644 --- a/FluidNC/src/I2SOut.cpp +++ b/FluidNC/src/I2SOut.cpp @@ -15,25 +15,10 @@ uint8_t i2s_out_read(pinnum_t pin) { return 0; } void i2s_out_write(pinnum_t pin, uint8_t val) {} -void i2s_out_push_sample(uint32_t usec) {} -void i2s_out_push() {} void i2s_out_delay() {} -int i2s_out_set_passthrough() { - return 0; -} -i2s_out_pulser_status_t i2s_out_get_pulser_status() { - return PASSTHROUGH; -} -int i2s_out_set_stepping() { - return 0; -} -int i2s_out_set_pulse_period(uint32_t period) { - return 0; -} -int i2s_out_reset() { - return 0; -} +void IRAM_ATTR i2s_out_push_fifo(int count) {} + int i2s_out_init() { return -1; } @@ -77,36 +62,7 @@ static std::atomic i2s_out_port_data = ATOMIC_VAR_INIT(0); # endif -// -// Configrations for DMA connected I2S -// -// One DMA buffer transfer takes about 2 ms -// I2S_OUT_DMABUF_LEN / I2S_SAMPLE_SIZE x I2S_OUT_USEC_PER_PULSE -// = 2000 / 4 x 4 -// = 2000us = 2ms -// If I2S_OUT_DMABUF_COUNT is 5, it will take about 10 ms for all the DMA buffer transfers to finish. -// -// Increasing I2S_OUT_DMABUF_COUNT has the effect of preventing buffer underflow, -// but on the other hand, it leads to a delay with pulse and/or non-pulse-generated I/Os. -// The number of I2S_OUT_DMABUF_COUNT should be chosen carefully. -// -// Reference information: -// FreeRTOS task time slice = portTICK_PERIOD_MS = 1 ms (ESP32 FreeRTOS port) -// -const int I2S_SAMPLE_SIZE = 4; /* 4 bytes, 32 bits per sample */ -const int DMA_SAMPLE_COUNT = I2S_OUT_DMABUF_LEN / I2S_SAMPLE_SIZE; /* number of samples per buffer */ -const int SAMPLE_SAFE_COUNT = (20 / I2S_OUT_USEC_PER_PULSE); /* prevent buffer overrun ($0 should be less than or equal 20) */ - -typedef struct { - uint32_t** buffers; - uint32_t* current; - uint32_t rw_pos; - lldesc_t** desc; - xQueueHandle queue; -} i2s_out_dma_t; - -static i2s_out_dma_t o_dma; -static intr_handle_t i2s_out_isr_handle; +const int I2S_SAMPLE_SIZE = 4; /* 4 bytes, 32 bits per sample */ // inner lock static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; @@ -131,15 +87,10 @@ static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; static int i2s_out_initialized = 0; -static volatile uint32_t i2s_out_pulse_period; -static uint32_t i2s_out_remain_time_until_next_pulse; // Time remaining until the next pulse (μsec) - static pinnum_t i2s_out_ws_pin = 255; static pinnum_t i2s_out_bck_pin = 255; static pinnum_t i2s_out_data_pin = 255; -static volatile i2s_out_pulser_status_t i2s_out_pulser_status = PASSTHROUGH; - // outer lock static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; # define I2S_OUT_PULSER_ENTER_CRITICAL() \ @@ -170,20 +121,12 @@ static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; // // Internal functions // -static void IRAM_ATTR set_single_data(uint32_t portData) { - // Apply port data in real-time (static I2S) - I2S0.conf_single_data = portData << DATA_SHIFT; -} - -void IRAM_ATTR i2s_out_push() { - if (i2s_out_pulser_status == PASSTHROUGH) { - set_single_data(ATOMIC_LOAD(&i2s_out_port_data)); +void IRAM_ATTR i2s_out_push_fifo(int count) { + uint32_t portData = ATOMIC_LOAD(&i2s_out_port_data) << DATA_SHIFT; + for (int i = 0; i < count; i++) { + I2S0.fifo_wr = portData; } } -void IRAM_ATTR i2s_out_push_fifo() { - uint32_t portData = ATOMIC_LOAD(&i2s_out_port_data); - I2S0.fifo_wr = portData << DATA_SHIFT; -} static inline void i2s_out_reset_fifo_without_lock() { I2S0.conf.rx_fifo_reset = 1; @@ -192,33 +135,6 @@ static inline void i2s_out_reset_fifo_without_lock() { I2S0.conf.tx_fifo_reset = 0; } -static int i2s_clear_dma_buffer(lldesc_t* dma_desc, uint32_t port_data) { - uint32_t* buf = (uint32_t*)dma_desc->buf; - for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { - buf[i] = port_data; - } - // Restore the buffer length. - // The length may have been changed short when the data was filled in to prevent buffer overrun. - dma_desc->length = I2S_OUT_DMABUF_LEN; - return 0; -} - -static int i2s_clear_o_dma_buffers(uint32_t port_data) { - for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { - // Initialize DMA descriptor - o_dma.desc[buf_idx]->owner = 1; - o_dma.desc[buf_idx]->eof = 1; // set to 1 will trigger the interrupt - o_dma.desc[buf_idx]->sosf = 0; - o_dma.desc[buf_idx]->length = I2S_OUT_DMABUF_LEN; - o_dma.desc[buf_idx]->size = I2S_OUT_DMABUF_LEN; - o_dma.desc[buf_idx]->buf = (uint8_t*)o_dma.buffers[buf_idx]; - o_dma.desc[buf_idx]->offset = 0; - o_dma.desc[buf_idx]->qe.stqe_next = (lldesc_t*)((buf_idx < (I2S_OUT_DMABUF_COUNT - 1)) ? (o_dma.desc[buf_idx + 1]) : o_dma.desc[0]); - i2s_clear_dma_buffer(o_dma.desc[buf_idx], port_data); - } - return 0; -} - static int i2s_out_gpio_attach(pinnum_t ws, pinnum_t bck, pinnum_t data) { // Route the i2s pins to the appropriate GPIO gpio_route(data, I2S0O_DATA_OUT23_IDX); @@ -250,11 +166,6 @@ static int i2s_out_gpio_shiftout(uint32_t port_data) { static int i2s_out_stop() { I2S_OUT_ENTER_CRITICAL(); - // Stop FIFO DMA - I2S0.out_link.stop = 1; - - // Disconnect DMA from FIFO - I2S0.fifo_conf.dscr_en = 0; //Unset this bit to disable I2S DMA mode. (R/W) // stop TX module I2S0.conf.tx_start = 0; @@ -301,41 +212,13 @@ static int i2s_out_start() { I2S0.conf.rx_reset = 1; I2S0.conf.rx_reset = 0; - // reset DMA - I2S0.lc_conf.in_rst = 1; - I2S0.lc_conf.in_rst = 0; - I2S0.lc_conf.out_rst = 1; - I2S0.lc_conf.out_rst = 0; - - I2S0.out_link.addr = (uint32_t)o_dma.desc[0]; - // reset FIFO i2s_out_reset_fifo_without_lock(); - // start DMA link - if (i2s_out_pulser_status == PASSTHROUGH) { -# if 0 - I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) -# else - I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) -# endif - I2S0.conf_single_data = port_data; - } else { - I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf_single_data = 0; - } - - I2S0.conf1.tx_stop_en = 1; // BCK and WCK are suppressed while FIFO is empty - - // Connect DMA to FIFO -# if 0 - I2S0.fifo_conf.dscr_en = 1; // Set this bit to enable I2S DMA mode. (R/W) -# else - I2S0.fifo_conf.dscr_en = 0; // Set this bit to enable I2S DMA mode. (R/W) -# endif + I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) + I2S0.conf1.tx_stop_en = 1; // BCK and WCK are suppressed while FIFO is empty - I2S0.int_clr.val = 0xFFFFFFFF; - I2S0.out_link.start = 1; + I2S0.int_clr.val = 0xFFFFFFFF; I2S0.conf.tx_start = 1; // Wait for the first FIFO data to prevent the unintentional generation of 0 data @@ -347,209 +230,14 @@ static int i2s_out_start() { return 0; } -// Fill out one DMA buffer -// Call with the I2S_OUT_PULSER lock acquired. -// Note that the lock is temporarily released while calling the callback function. -static int i2s_fillout_dma_buffer(lldesc_t* dma_desc) { - uint32_t* buf = (uint32_t*)dma_desc->buf; - o_dma.rw_pos = 0; - // It reuses the oldest (just transferred) buffer with the name "current" - // and fills the buffer for later DMA. - if (i2s_out_pulser_status == STEPPING) { - // - // Fillout the buffer for pulse - // - // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) - // is adjusted to be in a single buffer. - // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. - // Therefore, if a buffer is close to full and it is time to generate a pulse, - // the generation of the buffer is interrupted (the buffer length is shortened slightly) - // and the pulse generation is postponed until the next buffer is filled. - // - o_dma.rw_pos = 0; - while (o_dma.rw_pos < (DMA_SAMPLE_COUNT - SAMPLE_SAFE_COUNT)) { - // no data to read (buffer empty) - if (i2s_out_remain_time_until_next_pulse < I2S_OUT_USEC_PER_PULSE) { - // pulser status may change in pulse phase func, so I need to check it every time. - if (i2s_out_pulser_status == STEPPING) { - // fillout future DMA buffer (tail of the DMA buffer chains) - uint32_t old_rw_pos = o_dma.rw_pos; - I2S_OUT_PULSER_EXIT_CRITICAL(); // Temporarily unlocked status lock as it may be locked in pulse callback. - Stepper::pulse_func(); - - I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock again. - // Calculate pulse period. - i2s_out_remain_time_until_next_pulse += i2s_out_pulse_period - I2S_OUT_USEC_PER_PULSE * (o_dma.rw_pos - old_rw_pos); - if (i2s_out_pulser_status == WAITING) { - // i2s_out_set_passthrough() has called from the pulse function. - // It needs to go into pass-through mode. - // This DMA descriptor must be a tail of the chain. - dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. - } else if (i2s_out_pulser_status == PASSTHROUGH) { - // i2s_out_reset() has called during the execution of the pulse function. - // I2S has already in static mode, and buffers has cleared to zero. - // To prevent the pulse function from being called back, - // we assume that the buffer is already full. - i2s_out_remain_time_until_next_pulse = 0; // There is no need to fill the current buffer. - o_dma.rw_pos = DMA_SAMPLE_COUNT; // The buffer is full. - break; - } - continue; - } - } - // no pulse data in push buffer (pulse off or idle or callback is not defined) - buf[o_dma.rw_pos++] = ATOMIC_LOAD(&i2s_out_port_data); - if (i2s_out_remain_time_until_next_pulse >= I2S_OUT_USEC_PER_PULSE) { - i2s_out_remain_time_until_next_pulse -= I2S_OUT_USEC_PER_PULSE; - } - } - // set filled length to the DMA descriptor - dma_desc->length = o_dma.rw_pos * I2S_SAMPLE_SIZE; - } else if (i2s_out_pulser_status == WAITING) { - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. - } else { - // Stepper paused (passthrough state, static I2S control mode) - // In the passthrough mode, there is no need to fill the buffer with port_data. - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - i2s_out_remain_time_until_next_pulse = 0; - } - - return 0; -} - -// -// I2S out DMA Interrupts handler -// -static void IRAM_ATTR i2s_out_intr_handler(void* arg) { - lldesc_t* finish_desc; - portBASE_TYPE high_priority_task_awoken = pdFALSE; - - if (I2S0.int_st.out_eof || I2S0.int_st.out_total_eof) { - if (I2S0.int_st.out_total_eof) { - // This is tail of the DMA descriptors - I2S_OUT_ENTER_CRITICAL_ISR(); - // Stop FIFO DMA - I2S0.out_link.stop = 1; - // Disconnect DMA from FIFO - I2S0.fifo_conf.dscr_en = 0; //Unset this bit to disable I2S DMA mode. (R/W) - // Stop TX module - I2S0.conf.tx_start = 0; - I2S_OUT_EXIT_CRITICAL_ISR(); - } - // Get the descriptor of the last item in the linkedlist - finish_desc = (lldesc_t*)I2S0.out_eof_des_addr; - - // If the queue is full it's because we have an underflow, - // more than buf_count isr without new data, remove the front buffer - if (xQueueIsQueueFullFromISR(o_dma.queue)) { - lldesc_t* front_desc; - // Remove a descriptor from the DMA complete event queue - xQueueReceiveFromISR(o_dma.queue, &front_desc, &high_priority_task_awoken); - I2S_OUT_PULSER_ENTER_CRITICAL_ISR(); - uint32_t port_data = 0; - if (i2s_out_pulser_status == STEPPING) { - port_data = ATOMIC_LOAD(&i2s_out_port_data); - } - I2S_OUT_PULSER_EXIT_CRITICAL_ISR(); -# ifdef CONFIG_IDF_TARGET_ESP32 - // lldesc_t.buf is const for S2. Perhaps we can get by - // without replacing the data in the buffer since we are - // already in an error situation. - for (int i = 0; i < DMA_SAMPLE_COUNT; i++) { - front_desc->buf[i] = port_data; - } -# endif - front_desc->length = I2S_OUT_DMABUF_LEN; - } - - // Send a DMA complete event to the I2S bitstreamer task with finished buffer - xQueueSendFromISR(o_dma.queue, &finish_desc, &high_priority_task_awoken); - } - - if (high_priority_task_awoken == pdTRUE) { - portYIELD_FROM_ISR(); - } - - // clear interrupt - I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt -} - -// -// I2S bitstream generator task -// -static void i2sOutTask(void* parameter) { - lldesc_t* dma_desc; - while (1) { - // Wait a DMA complete event from I2S isr - // (Block until a DMA transfer has complete) - xQueueReceive(o_dma.queue, &dma_desc, portMAX_DELAY); - o_dma.current = (uint32_t*)(dma_desc->buf); - // It reuses the oldest (just transferred) buffer with the name "current" - // and fills the buffer for later DMA. - I2S_OUT_PULSER_ENTER_CRITICAL(); // Lock pulser status - if (i2s_out_pulser_status == STEPPING) { - // - // Fillout the buffer for pulse - // - // To avoid buffer overflow, all of the maximum pulse width (normaly about 10us) - // is adjusted to be in a single buffer. - // DMA_SAMPLE_SAFE_COUNT is referred to as the margin value. - // Therefore, if a buffer is close to full and it is time to generate a pulse, - // the generation of the buffer is interrupted (the buffer length is shortened slightly) - // and the pulse generation is postponed until the next buffer is filled. - // - i2s_fillout_dma_buffer(dma_desc); - dma_desc->length = o_dma.rw_pos * I2S_SAMPLE_SIZE; - } else if (i2s_out_pulser_status == WAITING) { - if (dma_desc->qe.stqe_next == NULL) { - // Tail of the DMA descriptor found - // I2S TX module has already stopped by ISR - i2s_out_stop(); - i2s_clear_o_dma_buffers(0); // 0 for static I2S control mode (right ch. data is always 0) - // You need to set the status before calling i2s_out_start() - // because the process in i2s_out_start() is different depending on the status. - i2s_out_pulser_status = PASSTHROUGH; - i2s_out_start(); - } else { - // Processing a buffer slightly ahead of the tail buffer. - // We don't need to fill up the buffer by port_data any more. - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - dma_desc->qe.stqe_next = NULL; // Cut the DMA descriptor ring. This allow us to identify the tail of the buffer. - } - } else { - // Stepper paused (passthrough state, static I2S control mode) - // In the passthrough mode, there is no need to fill the buffer with port_data. - i2s_clear_dma_buffer(dma_desc, 0); // Essentially, no clearing is required. I'll make sure I know when I've written something. - o_dma.rw_pos = 0; // If someone calls i2s_out_push_sample, make sure there is no buffer overflow - } - I2S_OUT_PULSER_EXIT_CRITICAL(); // Unlock pulser status - - static UBaseType_t uxHighWaterMark = 0; -# ifdef DEBUG_TASK_STACK - reportTaskStackSize(uxHighWaterMark); -# endif - } -} - // // External funtions // void i2s_out_delay() { I2S_OUT_PULSER_ENTER_CRITICAL(); - if (i2s_out_pulser_status == PASSTHROUGH) { - // Depending on the timing, it may not be reflected immediately, - // so wait twice as long just in case. - delay_us(I2S_OUT_USEC_PER_PULSE * 2); - } else { - // Just wait until the data now registered in the DMA descripter - // is reflected in the I2S TX module via FIFO. - // XXX perhaps just wait until I2SO.conf1.tx_start == 0 - delay_ms(I2S_OUT_DELAY_MS); - } + // Depending on the timing, it may not be reflected immediately, + // so wait twice as long just in case. + delay_us(I2S_OUT_USEC_PER_PULSE * 2); I2S_OUT_PULSER_EXIT_CRITICAL(); } @@ -567,109 +255,6 @@ uint8_t i2s_out_read(pinnum_t pin) { return (!!(port_data & bitnum_to_mask(pin))); } -void IRAM_ATTR i2s_out_push_sample(uint32_t usec) { - int32_t num = usec / I2S_OUT_USEC_PER_PULSE; - - if (num > SAMPLE_SAFE_COUNT) { - return; - } - // push at least one sample, even if num is zero) - if (num == 0) { - num = 1; - } - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); - do { - o_dma.current[o_dma.rw_pos++] = port_data; - } while (--num); -} - -i2s_out_pulser_status_t i2s_out_get_pulser_status() { - I2S_OUT_PULSER_ENTER_CRITICAL(); - i2s_out_pulser_status_t s = i2s_out_pulser_status; - I2S_OUT_PULSER_EXIT_CRITICAL(); - return s; -} - -int IRAM_ATTR i2s_out_set_passthrough() { - I2S_OUT_PULSER_ENTER_CRITICAL(); - // Triggers a change of mode if it is compiled to use I2S stream. - // The mode is not changed directly by this function. - // Pull the trigger - if (i2s_out_pulser_status == STEPPING) { - i2s_out_pulser_status = WAITING; // Start stopping the pulser (trigger) - } - // It is a function that may be called via i2sOutTask(). - // (i2sOutTask() -> Stepper::pulse_func() -> Stepper::go_idle() -> Stepper_Timer_Stop() -> this function) - // And only i2sOutTask() can change the state to PASSTHROUGH. - // So, to change the state, you need to return to i2sOutTask() as soon as possible. - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; -} - -int i2s_out_set_stepping() { - I2S_OUT_PULSER_ENTER_CRITICAL(); - if (i2s_out_pulser_status == STEPPING) { - // Re-entered (fail safe) - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; - } - - if (i2s_out_pulser_status == WAITING) { - // Wait for complete DMAs - for (;;) { - I2S_OUT_PULSER_EXIT_CRITICAL(); - delay_ms(I2S_OUT_DELAY_DMABUF_MS); - I2S_OUT_PULSER_ENTER_CRITICAL(); - if (i2s_out_pulser_status == WAITING) { - continue; - } - if (i2s_out_pulser_status == PASSTHROUGH) { - // DMA completed - break; - } - // Another function change the I2S state to STEPPING - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; - } - // Now, DMA completed. Fallthrough. - } - - // Change I2S state from PASSTHROUGH to STEPPING - i2s_out_stop(); - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); - i2s_clear_o_dma_buffers(port_data); - - // You need to set the status before calling i2s_out_start() - // because the process in i2s_out_start() is different depending on the status. - i2s_out_pulser_status = STEPPING; - i2s_out_start(); - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; -} - -int IRAM_ATTR i2s_out_set_pulse_period(uint32_t period) { - i2s_out_pulse_period = period; - return 0; -} - -int i2s_out_reset() { - I2S_OUT_PULSER_ENTER_CRITICAL(); - i2s_out_stop(); - if (i2s_out_pulser_status == STEPPING) { - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); - i2s_clear_o_dma_buffers(port_data); - } else if (i2s_out_pulser_status == WAITING) { - i2s_clear_o_dma_buffers(0); - i2s_out_pulser_status = PASSTHROUGH; - } - - // You need to set the status before calling i2s_out_start() - // because the process in i2s_out_start() is different depending on the status. - i2s_out_start(); - I2S_OUT_PULSER_EXIT_CRITICAL(); - return 0; -} - // // Initialize function (external function) // @@ -713,49 +298,10 @@ int i2s_out_init(i2s_out_init_t& init_param) { * M = 2 */ - // Allocate the array of pointers to the buffers - o_dma.buffers = (uint32_t**)malloc(sizeof(uint32_t*) * I2S_OUT_DMABUF_COUNT); - if (o_dma.buffers == nullptr) { - return -1; - } - - // Allocate each buffer that can be used by the DMA controller - for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { - o_dma.buffers[buf_idx] = (uint32_t*)heap_caps_calloc(1, I2S_OUT_DMABUF_LEN, MALLOC_CAP_DMA); - if (o_dma.buffers[buf_idx] == nullptr) { - return -1; - } - } - - // Allocate the array of DMA descriptors - o_dma.desc = (lldesc_t**)malloc(sizeof(lldesc_t*) * I2S_OUT_DMABUF_COUNT); - if (o_dma.desc == nullptr) { - return -1; - } - - // Allocate each DMA descriptor that will be used by the DMA controller - for (int buf_idx = 0; buf_idx < I2S_OUT_DMABUF_COUNT; buf_idx++) { - o_dma.desc[buf_idx] = (lldesc_t*)heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA); - if (o_dma.desc[buf_idx] == nullptr) { - return -1; - } - } - - // Initialize - i2s_clear_o_dma_buffers(init_param.init_val); - o_dma.rw_pos = 0; - o_dma.current = NULL; - o_dma.queue = xQueueCreate(I2S_OUT_DMABUF_COUNT, sizeof(uint32_t*)); - - // Set the first DMA descriptor - I2S0.out_link.addr = (uint32_t)o_dma.desc[0]; - // stop i2s I2S0.out_link.stop = 1; I2S0.conf.tx_start = 0; - I2S0.int_clr.val = I2S0.int_st.val; //clear pending interrupt - // // i2s_param_config // @@ -768,26 +314,10 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.conf.rx_reset = 1; I2S0.conf.rx_reset = 0; - //reset dma - I2S0.lc_conf.in_rst = 1; // Set this bit to reset in DMA FSM. (R/W) - I2S0.lc_conf.in_rst = 0; - I2S0.lc_conf.out_rst = 1; // Set this bit to reset out DMA FSM. (R/W) - I2S0.lc_conf.out_rst = 0; - // A lot of the stuff below could probably be replaced by i2s_set_clk(); i2s_out_reset_fifo_without_lock(); - //Enable and configure DMA - I2S0.lc_conf.check_owner = 0; - I2S0.lc_conf.out_loop_test = 0; - I2S0.lc_conf.out_auto_wrback = 0; // Disable auto outlink-writeback when all the data has been transmitted - I2S0.lc_conf.out_data_burst_en = 0; - I2S0.lc_conf.outdscr_burst_en = 0; - I2S0.lc_conf.out_no_restart_clr = 0; - I2S0.lc_conf.indscr_burst_en = 0; - I2S0.lc_conf.out_eof_mode = 1; // I2S_OUT_EOF_INT generated when DMA has popped all data from the FIFO; - I2S0.conf2.lcd_en = 0; I2S0.conf2.camera_en = 0; # ifdef SOC_I2S_SUPPORTS_PDM_TX @@ -799,15 +329,7 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.fifo_conf.dscr_en = 0; - if (i2s_out_pulser_status == STEPPING) { - // Channel output mode - I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf_single_data = 0; - } else { - // Static output mode - I2S0.conf_chan.tx_chan_mod = 3; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf_single_data = init_param.init_val; - } + I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) # if I2S_OUT_NUM_BITS == 16 I2S0.fifo_conf.tx_fifo_mod = 0; // 0: 16-bit dual channel data, 3: 32-bit single channel data @@ -815,8 +337,8 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.sample_rate_conf.tx_bits_mod = 16; // default is 16-bits I2S0.sample_rate_conf.rx_bits_mod = 16; // default is 16-bits # else - I2S0.fifo_conf.tx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.fifo_conf.rx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data + I2S0.fifo_conf.tx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data + I2S0.fifo_conf.rx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data // Data width is 32-bit. Forgetting this setting will result in a 16-bit transfer. I2S0.sample_rate_conf.tx_bits_mod = 32; I2S0.sample_rate_conf.rx_bits_mod = 32; @@ -826,13 +348,9 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.conf_chan.rx_chan_mod = 1; // 1: right+right I2S0.conf.rx_mono = 0; -# if 0 - I2S0.fifo_conf.dscr_en = 1; //connect DMA to fifo -# else - I2S0.fifo_conf.dscr_en = 0; //connect DMA to fifo -# endif - I2S0.conf.tx_start = 0; - I2S0.conf.rx_start = 0; + I2S0.fifo_conf.dscr_en = 0; // FIFO is not connected to DMA + I2S0.conf.tx_start = 0; + I2S0.conf.rx_start = 0; I2S0.conf.tx_msb_right = 1; // Set this bit to place right-channel data at the MSB in the transmit FIFO. I2S0.conf.tx_right_first = 0; // Setting this bit allows the right-channel data to be sent first. @@ -882,29 +400,6 @@ int i2s_out_init(i2s_out_init_t& init_param) { I2S0.sample_rate_conf.tx_bck_div_num = 2; // minimum value of 2 defaults to 6 I2S0.sample_rate_conf.rx_bck_div_num = 2; - // Enable TX interrupts (DMA Interrupts) - I2S0.int_ena.out_eof = 1; // Triggered when rxlink has finished sending a packet. - I2S0.int_ena.out_dscr_err = 0; // Triggered when invalid rxlink descriptors are encountered. - I2S0.int_ena.out_total_eof = 1; // Triggered when all transmitting linked lists are used up. - I2S0.int_ena.out_done = 0; // Triggered when all transmitted and buffered data have been read. - - // default pulse callback period (μsec) - i2s_out_pulse_period = init_param.pulse_period; - - // Create the task that will feed the buffer - xTaskCreatePinnedToCore(i2sOutTask, - "I2SOutTask", - 4096, - NULL, - 3, - nullptr, - CONFIG_ARDUINO_RUNNING_CORE // must run the task on same core - ); - - // Allocate and Enable the I2S interrupt - esp_intr_alloc(ETS_I2S0_INTR_SOURCE, 0, i2s_out_intr_handler, nullptr, &i2s_out_isr_handle); - esp_intr_enable(i2s_out_isr_handle); - // Remember GPIO pin numbers i2s_out_ws_pin = init_param.ws_pin; i2s_out_bck_pin = init_param.bck_pin; diff --git a/FluidNC/src/I2SOut.h b/FluidNC/src/I2SOut.h index 4caad6618..2a29042b1 100644 --- a/FluidNC/src/I2SOut.h +++ b/FluidNC/src/I2SOut.h @@ -26,17 +26,10 @@ /* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ const uint32_t I2S_OUT_USEC_PER_PULSE = 4; -// This value is empirically determined. It might depend on I2S_OUT_USEC_PULSE but -// the root cause of the limitation has not been analyzed so that is just a guess. -const uint32_t I2S_STREAM_MAX_USEC_PER_PULSE = 20; - -constexpr uint32_t i2s_out_max_steps_per_sec = 1000000 / (2 * I2S_OUT_USEC_PER_PULSE); - -const int I2S_OUT_DMABUF_COUNT = 5; /* number of DMA buffers to store data */ -const int I2S_OUT_DMABUF_LEN = 2000; /* maximum size in bytes (4092 is DMA's limit) */ - -const int I2S_OUT_DELAY_DMABUF_MS = (I2S_OUT_DMABUF_LEN / sizeof(uint32_t) * I2S_OUT_USEC_PER_PULSE / 1000); -const int I2S_OUT_DELAY_MS = (I2S_OUT_DELAY_DMABUF_MS * (I2S_OUT_DMABUF_COUNT + 1)); +// The longest pulse that we allow when using I2S. It is affected by the +// FIFO depth and could probably be a bit longer, but empirically this is +// enough for all known stepper drivers. +const uint32_t I2S_MAX_USEC_PER_PULSE = 20; typedef struct { /* @@ -84,10 +77,6 @@ int i2s_out_init(); */ uint8_t i2s_out_read(pinnum_t pin); -void i2s_out_push(); - -void i2s_out_push_fifo(); - /* Set a bit in the internal pin state var. (not written electrically) pin: expanded pin No. (0..31) @@ -96,32 +85,11 @@ void i2s_out_push_fifo(); void i2s_out_write(pinnum_t pin, uint8_t val); /* - Set current pin state to the I2S bitstream buffer - (This call will generate a future I2S_OUT_USEC_PER_PULSE μs x N bitstream) - usec: The length of time that the pulse should be repeated. - That time will be converted to an integer number of pulses of - length I2S_OUT_USEC_PER_PULSE. - The number of samples is limited to (20 / I2S_OUT_USEC_PER_PULSE). - return: number of pushed samples - 0 .. no space for push - */ -void i2s_out_push_sample(uint32_t usec); - -/* - Set pulser mode to passtrough - After this function is called, - the callback function to generate the pulse data - will not be called. - */ -int i2s_out_set_passthrough(); - -/* - Set pulser mode to stepping - After this function is called, - the callback function to generate stepping pulse data - will be called. - */ -int i2s_out_set_stepping(); + Push the I2S output value that was constructed by a series of + i2s_out_write() calls to the I2S FIFO so it will be shifted out + count: Number of repetitions +*/ +void i2s_out_push_fifo(int count); /* Dynamically delay until the Shift Register Pin changes @@ -129,29 +97,6 @@ int i2s_out_set_stepping(); */ void i2s_out_delay(); -/* - Set the pulse callback period in microseconds - */ -int i2s_out_set_pulse_period(uint32_t usec); - -/* - Get current pulser mode - */ -enum i2s_out_pulser_status_t { - PASSTHROUGH = 0, // Static I2S mode.The i2s_out_write() reflected with very little delay - STEPPING, // Streaming step data. - WAITING, // Waiting for the step DMA completion -}; -i2s_out_pulser_status_t IRAM_ATTR i2s_out_get_pulser_status(); - -/* - Reset i2s I/O expander - - Stop ISR/DMA - - Clear DMA buffer with the current expanded GPIO bits - - Retart ISR/DMA - */ -int i2s_out_reset(); - /* Reference: "ESP32 Technical Reference Manual" by Espressif Systems https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf diff --git a/FluidNC/src/MotionControl.cpp b/FluidNC/src/MotionControl.cpp index 243b9532f..937d03c07 100644 --- a/FluidNC/src/MotionControl.cpp +++ b/FluidNC/src/MotionControl.cpp @@ -11,7 +11,6 @@ #include "Report.h" // report_over_counter #include "Protocol.h" // protocol_execute_realtime #include "Planner.h" // plan_reset, etc -#include "I2SOut.h" // i2s_out_reset #include "Platform.h" // WEAK_LINK #include "Settings.h" // coords diff --git a/FluidNC/src/Pins/I2SOPinDetail.cpp b/FluidNC/src/Pins/I2SOPinDetail.cpp index b6a5f1db5..0d72fc42e 100644 --- a/FluidNC/src/Pins/I2SOPinDetail.cpp +++ b/FluidNC/src/Pins/I2SOPinDetail.cpp @@ -50,7 +50,7 @@ namespace Pins { _lastWrittenValue = high; i2s_out_write(_index, _inverted ^ (bool)high); - i2s_out_push(); + i2s_out_push_fifo(1); i2s_out_delay(); } } diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index ecfd4151b..1e7670031 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -169,7 +169,7 @@ namespace Machine { bool direction = dir ^ m->dir_invert; if (_engine == RMT_ENGINE || _engine == TIMED) { gpio_write(pin, direction); - } else if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + } else if (_engine == I2S_STREAM || _engine == I2S_STATIC) { i2s_out_write(pin, direction); } } @@ -203,7 +203,7 @@ namespace Machine { RMT.chnconf0[pin].mem_rd_rst_n = 0; RMT.chnconf0[pin].tx_start_n = 1; #endif - } else if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + } else if (_engine == I2S_STREAM || _engine == I2S_STATIC) { i2s_out_write(pin, !inverted); } else if (_engine == TIMED) { gpio_write(pin, !inverted); @@ -213,17 +213,8 @@ namespace Machine { } } // Do not use switch() in IRAM - if (_engine == stepper_id_t::I2S_STREAM) { - // Generate the number of pulses needed to span pulse_microseconds - i2s_out_push_sample(_pulseUsecs); - } else if (_engine == stepper_id_t::I2S_STATIC) { - // i2s_out_push(); - for (int i = 0; i < _i2sPulseCounts; i++) { - i2s_out_push_fifo(); - } -#if 0 - _stepPulseEndTime = usToEndTicks(_pulseUsecs); -#endif + if (_engine == I2S_STREAM || _engine == I2S_STATIC) { + i2s_out_push_fifo(_i2sPulseCounts); } else if (_engine == stepper_id_t::TIMED) { _stepPulseEndTime = usToEndTicks(_pulseUsecs); } @@ -235,18 +226,16 @@ namespace Machine { if (_engine == RMT_ENGINE) { return; } -#if 0 - if (_engine == I2S_STATIC || _engine == TIMED) { // Wait pulse + if (_engine == TIMED) { // Wait pulse spinUntil(_stepPulseEndTime); } -#endif for (size_t axis = 0; axis < _n_active_axes; axis++) { for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { auto m = axis_motors[axis][motor]; if (m) { int pin = m->step_pin; bool inverted = m->step_invert; - if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + if (_engine == I2S_STREAM || _engine == I2S_STATIC) { i2s_out_write(pin, inverted); } else if (_engine == TIMED) { gpio_write(pin, inverted); @@ -254,50 +243,24 @@ namespace Machine { } } } - if (_engine == stepper_id_t::I2S_STATIC) { - for (int i = 0; i < _i2sPulseCounts; i++) { - i2s_out_push_fifo(); - } + if (_engine == I2S_STREAM || _engine == I2S_STATIC) { + i2s_out_push_fifo(_i2sPulseCounts); } } - void Stepping::reset() { - if (_engine == I2S_STREAM) { - i2s_out_reset(); - } - } - void Stepping::beginLowLatency() { - _switchedStepper = _engine == I2S_STREAM; - if (_switchedStepper) { - _engine = I2S_STATIC; - i2s_out_set_passthrough(); - i2s_out_delay(); // Wait for a change in mode. - } - } - void Stepping::endLowLatency() { - if (_switchedStepper) { - if (i2s_out_get_pulser_status() != PASSTHROUGH) { - // Called during streaming. Stop streaming. - // log_debug("Stop the I2S streaming and switch to the passthrough mode."); - i2s_out_set_passthrough(); - i2s_out_delay(); // Wait for a change in mode. - } - _engine = I2S_STREAM; - } - } + void Stepping::reset() {} + void Stepping::beginLowLatency() {} + void Stepping::endLowLatency() {} // Called only from step() void IRAM_ATTR Stepping::waitDirection() { if (_directionDelayUsecs) { // Stepper drivers need some time between changing direction and doing a pulse. // Do not use switch() in IRAM - if (_engine == stepper_id_t::I2S_STREAM) { - // Commit the pin changes to the DMA queue - i2s_out_push_sample(_directionDelayUsecs); - } else if (_engine == stepper_id_t::I2S_STATIC) { + if (_engine == stepper_id_t::I2S_STREAM || _engine == stepper_id_t::I2S_STATIC) { // Commit the pin changes to the hardware immediately // i2s_out_push(); - i2s_out_push_fifo(); + i2s_out_push_fifo(1); delay_us(_directionDelayUsecs); } else if (_engine == stepper_id_t::TIMED) { // If we are using RMT, we can't delay here. @@ -309,30 +272,16 @@ namespace Machine { // Called only from Stepper::pulse_func when a new segment is loaded // The argument is in units of ticks of the timer that generates ISRs void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { - if (_engine == I2S_STREAM) { - // Pulse ISR is called for each tick of alarm_val. - // The argument to i2s_out_set_pulse_period is in units of microseconds - i2s_out_set_pulse_period(((uint32_t)timerTicks) / ticksPerMicrosecond); - } else { - stepTimerSetTicks((uint32_t)timerTicks); - } + stepTimerSetTicks((uint32_t)timerTicks); } // Called only from Stepper::wake_up which is not used in ISR context void Stepping::startTimer() { - if (_engine == I2S_STREAM) { - i2s_out_set_stepping(); - } else { - stepTimerStart(); - } + stepTimerStart(); } // Called only from Stepper::stop_stepping, used in both ISR and foreground contexts void IRAM_ATTR Stepping::stopTimer() { - if (_engine == I2S_STREAM) { - i2s_out_set_passthrough(); - } else { - stepTimerStop(); - } + stepTimerStop(); } void Stepping::group(Configuration::HandlerBase& handler) { @@ -351,27 +300,33 @@ namespace Machine { log_warn("Increasing stepping/pulse_us to the IS2 minimum value " << I2S_OUT_USEC_PER_PULSE); _pulseUsecs = I2S_OUT_USEC_PER_PULSE; } - if ((_engine == I2S_STATIC || _engine == I2S_STREAM) && _pulseUsecs > I2S_STREAM_MAX_USEC_PER_PULSE) { - log_warn("Decreasing stepping/pulse_us to " << I2S_STREAM_MAX_USEC_PER_PULSE << ", the maximum value for I2S_STREAM"); - _pulseUsecs = I2S_STREAM_MAX_USEC_PER_PULSE; + if ((_engine == I2S_STREAM || _engine == I2S_STATIC) && _pulseUsecs > I2S_MAX_USEC_PER_PULSE) { + log_warn("Decreasing stepping/pulse_us to " << I2S_MAX_USEC_PER_PULSE << ", the maximum value for I2S"); + _pulseUsecs = I2S_MAX_USEC_PER_PULSE; } } - if (_engine == I2S_STATIC || _engine == I2S_STREAM) { + if (_engine == I2S_STREAM || _engine == I2S_STATIC) { // Number of I2S frames for a pulse, rounded up _i2sPulseCounts = (_pulseUsecs + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; } } uint32_t Stepping::maxPulsesPerSec() { + uint32_t pps; switch (_engine) { - case stepper_id_t::I2S_STREAM: - case stepper_id_t::I2S_STATIC: - return 1000000 / ((2 * _i2sPulseCounts) * I2S_OUT_USEC_PER_PULSE); - case stepper_id_t::RMT_ENGINE: - return 1000000 / (2 * _pulseUsecs + _directionDelayUsecs); - case stepper_id_t::TIMED: + case I2S_STREAM: + case I2S_STATIC: + pps = 1000000 / ((2 * _i2sPulseCounts) * I2S_OUT_USEC_PER_PULSE); + break; + case RMT_ENGINE: + pps = 1000000 / (2 * _pulseUsecs + _directionDelayUsecs); + // fall through + case TIMED: default: - return 80000; // based on testing + if (pps > 80000) { // Based on testing + pps = 80000; + } } + return pps; } } From 2ae7173699bd229c5ef1f0ff8a0bb6d98fa85fd7 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Wed, 9 Oct 2024 07:10:22 -1000 Subject: [PATCH 11/30] Created a step_engine abstraction in C --- FluidNC/esp32/gpio.cpp | 373 +------------ FluidNC/esp32/gpio_dump.cpp | 358 +++++++++++++ FluidNC/esp32/i2s_engine.c | 499 ++++++++++++++++++ FluidNC/esp32/rmt_engine.c | 133 +++++ FluidNC/esp32/timed_engine.c | 73 +++ FluidNC/include/Driver/delay_usecs.h | 10 +- FluidNC/include/Driver/fluidnc_gpio.h | 27 +- FluidNC/include/Driver/gpio_dump.h | 5 + .../I2SOut.h => include/Driver/i2s_out.h} | 29 +- FluidNC/src/I2SOut.cpp | 454 ---------------- FluidNC/src/Machine/Axis.cpp | 7 +- FluidNC/src/Machine/I2SOBus.cpp | 23 +- FluidNC/src/Pins/GPIOPinDetail.cpp | 2 +- FluidNC/src/Pins/GPIOPinDetail.h | 2 +- FluidNC/src/Pins/I2SOPinDetail.cpp | 5 +- FluidNC/src/ProcessSettings.cpp | 2 +- FluidNC/src/Stepping.cpp | 388 +++++--------- FluidNC/src/Stepping.h | 4 + platformio.ini | 1 + 19 files changed, 1285 insertions(+), 1110 deletions(-) create mode 100644 FluidNC/esp32/gpio_dump.cpp create mode 100644 FluidNC/esp32/i2s_engine.c create mode 100644 FluidNC/esp32/rmt_engine.c create mode 100644 FluidNC/esp32/timed_engine.c create mode 100644 FluidNC/include/Driver/gpio_dump.h rename FluidNC/{src/I2SOut.h => include/Driver/i2s_out.h} (76%) delete mode 100644 FluidNC/src/I2SOut.cpp diff --git a/FluidNC/esp32/gpio.cpp b/FluidNC/esp32/gpio.cpp index a72c3afc4..394960835 100644 --- a/FluidNC/esp32/gpio.cpp +++ b/FluidNC/esp32/gpio.cpp @@ -8,19 +8,15 @@ #include "driver/gpio.h" #include "hal/gpio_hal.h" -#include "src/Protocol.h" - -#include - static gpio_dev_t* _gpio_dev = GPIO_HAL_GET_HW(GPIO_PORT_0); -void IRAM_ATTR gpio_write(pinnum_t pin, bool value) { +void IRAM_ATTR gpio_write(pinnum_t pin, int value) { gpio_ll_set_level(_gpio_dev, (gpio_num_t)pin, value); } -bool IRAM_ATTR gpio_read(pinnum_t pin) { +int IRAM_ATTR gpio_read(pinnum_t pin) { return gpio_ll_get_level(_gpio_dev, (gpio_num_t)pin); } -void gpio_mode(pinnum_t pin, bool input, bool output, bool pullup, bool pulldown, bool opendrain) { +void gpio_mode(pinnum_t pin, int input, int output, int pullup, int pulldown, int opendrain) { gpio_config_t conf = { .pin_bit_mask = (1ULL << pin), .intr_type = GPIO_INTR_DISABLE }; if (input) { @@ -96,10 +92,10 @@ static inline gpio_mask_t get_gpios() { static gpio_mask_t gpio_mask(int gpio_num) { return 1ULL << gpio_num; } -static inline bool gpio_is_active(int gpio_num) { +static inline int gpio_is_active(int gpio_num) { return get_gpios() & gpio_mask(gpio_num); } -static void gpios_update(gpio_mask_t& gpios, int gpio_num, bool active) { +static void gpios_update(gpio_mask_t& gpios, int gpio_num, int active) { if (active) { gpios |= gpio_mask(gpio_num); } else { @@ -110,14 +106,14 @@ static void gpios_update(gpio_mask_t& gpios, int gpio_num, bool active) { static gpio_dispatch_t gpioActions[GPIO_NUM_MAX + 1] = { nullptr }; static void* gpioArgs[GPIO_NUM_MAX + 1]; -void gpio_set_action(int gpio_num, gpio_dispatch_t action, void* arg, bool invert) { +void gpio_set_action(int gpio_num, gpio_dispatch_t action, void* arg, int invert) { gpioActions[gpio_num] = action; gpioArgs[gpio_num] = arg; gpio_mask_t mask = gpio_mask(gpio_num); gpios_update(gpios_interest, gpio_num, true); gpios_update(gpios_inverted, gpio_num, invert); gpio_set_rate_limit(gpio_num, 5); - bool active = gpio_is_active(gpio_num); + int active = gpio_is_active(gpio_num); // Set current to the opposite of the current state so the first poll will send the current state gpios_update(gpios_current, gpio_num, !active); @@ -128,7 +124,7 @@ void gpio_clear_action(int gpio_num) { gpios_update(gpios_interest, gpio_num, false); } -static void gpio_send_action(int gpio_num, bool active) { +static void gpio_send_action(int gpio_num, int active) { auto end_ticks = gpio_next_event_ticks[gpio_num]; int32_t this_ticks = int32_t(xTaskGetTickCount()); if (end_ticks == 0 || ((this_ticks - end_ticks) > 0)) { @@ -159,356 +155,3 @@ void poll_gpios() { } } } - -// Support functions for gpio_dump -static bool exists(gpio_num_t gpio) { - if (gpio == 20) { - // GPIO20 is listed in GPIO_PIN_MUX_REG[] but it is only - // available on the ESP32-PICO-V3 package. - return false; - } - return GPIO_PIN_MUX_REG[gpio]; // Missing GPIOs have 0 entries in this array -} -static bool output_level(gpio_num_t gpio) { - if (gpio < 32) { - return REG_READ(GPIO_OUT_REG) & (1 << gpio); - } else { - return REG_READ(GPIO_OUT1_REG) & (1 << (gpio - 32)); - } -} - -static bool is_input(gpio_num_t gpio) { - return GET_PERI_REG_MASK(GPIO_PIN_MUX_REG[gpio], FUN_IE); -} -static bool is_output(gpio_num_t gpio) { - if (gpio < 32) { - return GET_PERI_REG_MASK(GPIO_ENABLE_REG, 1 << gpio); - } else { - return GET_PERI_REG_MASK(GPIO_ENABLE1_REG, 1 << (gpio - 32)); - } -} -static int gpio_function(gpio_num_t gpio) { - return REG_GET_FIELD(GPIO_PIN_MUX_REG[gpio], MCU_SEL); -} -static uint32_t gpio_out_sel(gpio_num_t gpio) { - return REG_READ(GPIO_FUNC0_OUT_SEL_CFG_REG + (gpio * 4)); -} -static uint32_t gpio_in_sel(int function) { - return REG_READ(GPIO_FUNC0_IN_SEL_CFG_REG + (function * 4)); -} - -// another way to determine available gpios is the array GPIO_PIN_MUX_REG[SOC_GPIO_PIN_COUNT] -// which has 0 in unavailable slots, see soc/gpio_periph.{ch} -std::vector avail_gpios = { 0, 1, 3, 4, 5, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33, 34, 35, 36, 39 }; - -struct pin_mux { - int pinnum; - const char* pinname; - const char* functions[6]; -} const pins[] = { - { 0, "GPIO0", { "GPIO0", "CLK_OUT1", "GPIO0", "-", "-", "EMAC_TX_CLK" } }, - { 1, "U0TXD", { "U0TXD", "CLK_OUT3", "GPIO1", "-", "-", "EMAC_RXD2" } }, - { 2, "GPIO2", { "GPIO2", "HSPIWP", "GPIO2", "HS2_DATA0", "SD_DATA0", "-" } }, - { 3, "U0RXD", { "U0RXD", "CLK_OUT2", "GPIO3", "-", "-", "-" } }, - { 4, "GPIO4", { "GPIO4", "HSPIHD", "GPIO4", "HS2_DATA1", "SD_DATA1", "EMAC_TX_ER" } }, - { 5, "GPIO5", { "GPIO5", "VSPICS0", "GPIO5", "HS1_DATA6", "-", "EMAC_RX_CLK" } }, - { 6, "SD_CLK", { "SD_CLK", "SPICLK", "GPIO6", "HS1_CLK", "U1CTS", "-" } }, - { 7, "SD_DATA_0", { "SD_DATA0", "SPIQ", "GPIO7", "HS1_DATA0", "U2RTS", "-" } }, - { 8, "SD_DATA_1", { "SD_DATA1", "SPID", "GPIO8", "HS1_DATA1", "U2CTS", "-" } }, - { 9, "SD_DATA_2", { "SD_DATA2", "SPIHD", "GPIO9", "HS1_DATA2", "U1RXD", "-" } }, - { 10, "SD_DATA_3", { "SD_DATA3", "SPIWP", "GPIO10", "HS1_DATA3", "U1TXD", "-" } }, - { 11, "SD_CMD", { "SD_CMD", "SPICS0", "GPIO11", "HS1_CMD", "U1RTS", "-" } }, - { 12, "MTDI", { "MTDI", "HSPIQ", "GPIO12", "HS2_DATA2", "SD_DATA2", "EMAC_TXD3" } }, - { 13, "MTCK", { "MTCK", "HSPID", "GPIO13", "HS2_DATA3", "SD_DATA3", "EMAC_RX_ER" } }, - { 14, "MTMS", { "MTMS", "HSPICLK", "GPIO14", "HS2_CLK", "SD_CLK", "EMAC_TXD2" } }, - { 15, "MTDO", { "MTDO", "HSPICS0", "GPIO15", "HS2_CMD", "SD_CMD", "EMAC_RXD3" } }, - { 16, "GPIO16", { "GPIO16", "-", "GPIO16", "HS1_DATA4", "U2RXD", "EMAC_CLK_OUT1" } }, - { 17, "GPIO17", { "GPIO17", "-", "GPIO17", "HS1_DATA5", "U2TXD", "EMAC_CLK_1801" } }, - { 18, "GPIO18", { "GPIO18", "VSPICLK", "GPIO18", "HS1_DATA7", "-", "-" } }, - { 19, "GPIO19", { "GPIO19", "VSPIQ", "GPIO19", "U0CTS", "-", "EMAC_TXD0" } }, - { 21, "GPIO21", { "GPIO21", "VSPIHD", "GPIO21", "-", "-", "EMAC_TX_EN" } }, - { 22, "GPIO22", { "GPIO22", "VSPIWP", "GPIO22", "U0RTS", "-", "EMAC_TXD1" } }, - { 23, "GPIO23", { "GPIO23", "VSPID", "GPIO23", "HS1_STROBE", "-", "-" } }, - { 25, "GPIO25", { "GPIO25", "-", "GPIO25", "-", "-", "EMAC_RXD0" } }, - { 26, "GPIO26", { "GPIO26", "-", "GPIO26", "-", "-", "EMAC_RXD1" } }, - { 27, "GPIO27", { "GPIO27", "-", "GPIO27", "-", "-", "EMAC_RX_DV" } }, - { 32, "32K_XP", { "GPIO32", "-", "GPIO32", "-", "-", "-" } }, - { 33, "32K_XN", { "GPIO33", "-", "GPIO33", "-", "-", "-" } }, - { 34, "VDET_1", { "GPIO34", "-", "GPIO34", "-", "-", "-" } }, - { 35, "VDET_2", { "GPIO35", "-", "GPIO35", "-", "-", "-" } }, - { 36, "SENSOR_VP", { "GPIO36", "-", "GPIO36", "-", "-", "-" } }, - { 37, "SENSOR_CAPP", { "GPIO37", "-", "GPIO37", "-", "-", "-" } }, - { 38, "SENSOR_CAPN", { "GPIO38", "-", "GPIO38", "-", "-", "-" } }, - { 39, "SENSOR_VN", { "GPIO39", "-", "GPIO39", "-", "-", "-" } }, - { -1, "", { "" } }, -}; -const char* pin_function_name(gpio_num_t gpio, int function) { - const pin_mux* p; - for (p = pins; p->pinnum != -1; ++p) { - if (p->pinnum == gpio) { - return p->functions[function]; - } - } - return ""; -} - -struct gpio_matrix_t { - int num; - const char* in; - const char* out; - bool iomux; -} const gpio_matrix[] = { { 0, "SPICLK_in", "SPICLK_out", true }, - { 1, "SPIQ_in", "SPIQ_out", true }, - { 2, "SPID_in", "SPID_out", true }, - { 3, "SPIHD_in", "SPIHD_out", true }, - { 4, "SPIWP_in", "SPIWP_out", true }, - { 5, "SPICS0_in", "SPICS0_out", true }, - { 6, "SPICS1_in", "SPICS1_out", false }, - { 7, "SPICS2_in", "SPICS2_out", false }, - { 8, "HSPICLK_in", "HSPICLK_out", true }, - { 9, "HSPIQ_in", "HSPIQ_out", true }, - { 10, "HSPID_in", "HSPID_out", true }, - { 11, "HSPICS0_in", "HSPICS0_out", true }, - { 12, "HSPIHD_in", "HSPIHD_out", true }, - { 13, "HSPIWP_in", "HSPIWP_out", true }, - { 14, "U0RXD_in", "U0TXD_out", true }, - { 15, "U0CTS_in", "U0RTS_out", true }, - { 16, "U0DSR_in", "U0DTR_out", false }, - { 17, "U1RXD_in", "U1TXD_out", true }, - { 18, "U1CTS_in", "U1RTS_out", true }, - { 23, "I2S0O_BCK_in", "I2S0O_BCK_out", false }, - { 24, "I2S1O_BCK_in", "I2S1O_BCK_out", false }, - { 25, "I2S0O_WS_in", "I2S0O_WS_out", false }, - { 26, "I2S1O_WS_in", "I2S1O_WS_out", false }, - { 27, "I2S0I_BCK_in", "I2S0I_BCK_out", false }, - { 28, "I2S0I_WS_in", "I2S0I_WS_out", false }, - { 29, "I2CEXT0_SCL_in", "I2CEXT0_SCL_out", false }, - { 30, "I2CEXT0_SDA_in", "I2CEXT0_SDA_out", false }, - { 31, "pwm0_sync0_in", "sdio_tohost_int_out", false }, - { 32, "pwm0_sync1_in", "pwm0_out0a", false }, - { 33, "pwm0_sync2_in", "pwm0_out0b", false }, - { 34, "pwm0_f0_in", "pwm0_out1a", false }, - { 35, "pwm0_f1_in", "pwm0_out1b", false }, - { 36, "pwm0_f2_in", "pwm0_out2a", false }, - { 37, "", "pwm0_out2b", false }, - { 39, "pcnt_sig_ch0_in0", "", false }, - { 40, "pcnt_sig_ch1_in0", "", false }, - { 41, "pcnt_ctrl_ch0_in0", "", false }, - { 42, "pcnt_ctrl_ch1_in0", "", false }, - { 43, "pcnt_sig_ch0_in1", "", false }, - { 44, "pcnt_sig_ch1_in1", "", false }, - { 45, "pcnt_ctrl_ch0_in1", "", false }, - { 46, "pcnt_ctrl_ch1_in1", "", false }, - { 47, "pcnt_sig_ch0_in2", "", false }, - { 48, "pcnt_sig_ch1_in2", "", false }, - { 49, "pcnt_ctrl_ch0_in2", "", false }, - { 50, "pcnt_ctrl_ch1_in2", "", false }, - { 51, "pcnt_sig_ch0_in3", "", false }, - { 52, "pcnt_sig_ch1_in3", "", false }, - { 53, "pcnt_ctrl_ch0_in3", "", false }, - { 54, "pcnt_ctrl_ch1_in3", "", false }, - { 55, "pcnt_sig_ch0_in4", "", false }, - { 56, "pcnt_sig_ch1_in4", "", false }, - { 57, "pcnt_ctrl_ch0_in4", "", false }, - { 58, "pcnt_ctrl_ch1_in4", "", false }, - { 61, "HSPICS1_in", "HSPICS1_out", false }, - { 62, "HSPICS2_in", "HSPICS2_out", false }, - { 63, "VSPICLK_in", "VSPICLK_out_mux", true }, - { 64, "VSPIQ_in", "VSPIQ_out", true }, - { 65, "VSPID_in", "VSPID_out", true }, - { 66, "VSPIHD_in", "VSPIHD_out", true }, - { 67, "VSPIWP_in", "VSPIWP_out", true }, - { 68, "VSPICS0_in", "VSPICS0_out", true }, - { 69, "VSPICS1_in", "VSPICS1_out", false }, - { 70, "VSPICS2_in", "VSPICS2_out", false }, - { 71, "pcnt_sig_ch0_in5", "ledc_hs_sig_out0", false }, - { 72, "pcnt_sig_ch1_in5", "ledc_hs_sig_out1", false }, - { 73, "pcnt_ctrl_ch0_in5", "ledc_hs_sig_out2", false }, - { 74, "pcnt_ctrl_ch1_in5", "ledc_hs_sig_out3", false }, - { 75, "pcnt_sig_ch0_in6", "ledc_hs_sig_out4", false }, - { 76, "pcnt_sig_ch1_in6", "ledc_hs_sig_out5", false }, - { 77, "pcnt_ctrl_ch0_in6", "ledc_hs_sig_out6", false }, - { 78, "pcnt_ctrl_ch1_in6", "ledc_hs_sig_out7", false }, - { 79, "pcnt_sig_ch0_in7", "ledc_ls_sig_out0", false }, - { 80, "pcnt_sig_ch1_in7", "ledc_ls_sig_out1", false }, - { 81, "pcnt_ctrl_ch0_in7", "ledc_ls_sig_out2", false }, - { 82, "pcnt_ctrl_ch1_in7", "ledc_ls_sig_out3", false }, - { 83, "rmt_sig_in0", "ledc_ls_sig_out4", false }, - { 84, "rmt_sig_in1", "ledc_ls_sig_out5", false }, - { 85, "rmt_sig_in2", "ledc_ls_sig_out6", false }, - { 86, "rmt_sig_in3", "ledc_ls_sig_out7", false }, - { 87, "rmt_sig_in4", "rmtt_sig_out0", false }, - { 88, "rmt_sig_in5", "rmtt_sig_out1", false }, - { 89, "rmt_sig_in6", "rmtt_sig_out2", false }, - { 90, "rmt_sig_in7", "rmtt_sig_out3", false }, - { 91, "", "rmtt_sig_out4", false }, - { 92, "", "rmtt_sig_out5", false }, - { 93, "", "rmtt_sig_out6", false }, - { 94, "", "rmtt_sig_out7", false }, - { 95, "I2CEXT1_SCL_in", "I2CEXT1_SCL_out", false }, - { 96, "I2CEXT1_SDA_in", "I2CEXT1_SDA_out", false }, - { 97, "host_card_detect_n_1", "host_ccmd_od_pullup_en_n", false }, - { 98, "host_card_detect_n_2", "host_rst_n_1", false }, - { 99, "host_card_write_prt_1", "host_rst_n_2", false }, - { 100, "host_card_write_prt_2", "gpio_sd0_out", false }, - { 101, "host_card_int_n_1", "gpio_sd1_out", false }, - { 102, "host_card_int_n_2", "gpio_sd2_out", false }, - { 103, "pwm1_sync0_in", "gpio_sd3_out", false }, - { 104, "pwm1_sync1_in", "gpio_sd4_out", false }, - { 105, "pwm1_sync2_in", "gpio_sd5_out", false }, - { 106, "pwm1_f0_in", "gpio_sd6_out", false }, - { 107, "pwm1_f1_in", "gpio_sd7_out", false }, - { 108, "pwm1_f2_in", "pwm1_out0a", false }, - { 109, "pwm0_cap0_in", "pwm1_out0b", false }, - { 110, "pwm0_cap1_in", "pwm1_out1a", false }, - { 111, "pwm0_cap2_in", "pwm1_out1b", false }, - { 112, "pwm1_cap0_in", "pwm1_out2a", false }, - { 113, "pwm1_cap1_in", "pwm1_out2b", false }, - { 114, "pwm1_cap2_in", "", false }, - { 115, "", "", false }, - { 116, "", "", false }, - { 117, "", "", false }, - { 118, "", "", false }, - { 119, "", "", false }, - { 120, "", "", false }, - { 121, "", "", false }, - { 122, "", "", false }, - { 123, "", "", false }, - { 124, "", "", false }, - { 140, "I2S0I_DATA_in0", "I2S0O_DATA_out0", false }, - { 141, "I2S0I_DATA_in1", "I2S0O_DATA_out1", false }, - { 142, "I2S0I_DATA_in2", "I2S0O_DATA_out2", false }, - { 143, "I2S0I_DATA_in3", "I2S0O_DATA_out3", false }, - { 144, "I2S0I_DATA_in4", "I2S0O_DATA_out4", false }, - { 145, "I2S0I_DATA_in5", "I2S0O_DATA_out5", false }, - { 146, "I2S0I_DATA_in6", "I2S0O_DATA_out6", false }, - { 147, "I2S0I_DATA_in7", "I2S0O_DATA_out7", false }, - { 148, "I2S0I_DATA_in8", "I2S0O_DATA_out8", false }, - { 149, "I2S0I_DATA_in9", "I2S0O_DATA_out9", false }, - { 150, "I2S0I_DATA_in10", "I2S0O_DATA_out10", false }, - { 151, "I2S0I_DATA_in11", "I2S0O_DATA_out11", false }, - { 152, "I2S0I_DATA_in12", "I2S0O_DATA_out12", false }, - { 153, "I2S0I_DATA_in13", "I2S0O_DATA_out13", false }, - { 154, "I2S0I_DATA_in14", "I2S0O_DATA_out14", false }, - { 155, "I2S0I_DATA_in15", "I2S0O_DATA_out15", false }, - { 156, "", "I2S0O_DATA_out16", false }, - { 157, "", "I2S0O_DATA_out17", false }, - { 158, "", "I2S0O_DATA_out18", false }, - { 159, "", "I2S0O_DATA_out19", false }, - { 160, "", "I2S0O_DATA_out20", false }, - { 161, "", "I2S0O_DATA_out21", false }, - { 162, "", "I2S0O_DATA_out22", false }, - { 163, "", "I2S0O_DATA_out23", false }, - { 164, "I2S1I_BCK_in", "I2S1I_BCK_out", false }, - { 165, "I2S1I_WS_in", "I2S1I_WS_out", false }, - { 166, "I2S1I_DATA_in0", "I2S1O_DATA_out0", false }, - { 167, "I2S1I_DATA_in1", "I2S1O_DATA_out1", false }, - { 168, "I2S1I_DATA_in2", "I2S1O_DATA_out2", false }, - { 169, "I2S1I_DATA_in3", "I2S1O_DATA_out3", false }, - { 170, "I2S1I_DATA_in4", "I2S1O_DATA_out4", false }, - { 171, "I2S1I_DATA_in5", "I2S1O_DATA_out5", false }, - { 172, "I2S1I_DATA_in6", "I2S1O_DATA_out6", false }, - { 173, "I2S1I_DATA_in7", "I2S1O_DATA_out7", false }, - { 174, "I2S1I_DATA_in8", "I2S1O_DATA_out8", false }, - { 175, "I2S1I_DATA_in9", "I2S1O_DATA_out9", false }, - { 176, "I2S1I_DATA_in10", "I2S1O_DATA_out10", false }, - { 177, "I2S1I_DATA_in11", "I2S1O_DATA_out11", false }, - { 178, "I2S1I_DATA_in12", "I2S1O_DATA_out12", false }, - { 179, "I2S1I_DATA_in13", "I2S1O_DATA_out13", false }, - { 180, "I2S1I_DATA_in14", "I2S1O_DATA_out14", false }, - { 181, "I2S1I_DATA_in15", "I2S1O_DATA_out15", false }, - { 182, "", "I2S1O_DATA_out16", false }, - { 183, "", "I2S1O_DATA_out17", false }, - { 184, "", "I2S1O_DATA_out18", false }, - { 185, "", "I2S1O_DATA_out19", false }, - { 186, "", "I2S1O_DATA_out20", false }, - { 187, "", "I2S1O_DATA_out21", false }, - { 188, "", "I2S1O_DATA_out22", false }, - { 189, "", "I2S1O_DATA_out23", false }, - { 190, "I2S0I_H_SYNC", "", false }, - { 191, "I2S0I_V_SYNC", "", false }, - { 192, "I2S0I_H_ENABLE", "", false }, - { 193, "I2S1I_H_SYNC", "", false }, - { 194, "I2S1I_V_SYNC", "", false }, - { 195, "I2S1I_H_ENABLE", "", false }, - { 196, "", "", false }, - { 197, "", "", false }, - { 198, "U2RXD_in", "U2TXD_out", true }, - { 199, "U2CTS_in", "U2RTS_out", true }, - { 200, "emac_mdc_i", "emac_mdc_o", false }, - { 201, "emac_mdi_i", "emac_mdo_o", false }, - { 202, "emac_crs_i", "emac_crs_o", false }, - { 203, "emac_col_i", "emac_col_o", false }, - { 204, "pcmfsync_in", "bt_audio0_irq", false }, - { 205, "pcmclk_in", "bt_audio1_irq", false }, - { 206, "pcmdin", "bt_audio2_irq", false }, - { 207, "", "le_audio0_irq", false }, - { 208, "", "le_audio1_irq", false }, - { 209, "", "le_audio2_irq", false }, - { 210, "", "cmfsync_out", false }, - { 211, "", "cmclk_out", false }, - { 212, "", "cmdout", false }, - { 213, "", "le_audio_sync0_p", false }, - { 214, "", "le_audio_sync1_p", false }, - { 215, "", "le_audio_sync2_p", false }, - { 224, "", "ig_in_func224", false }, - { 225, "", "ig_in_func225", false }, - { 226, "", "ig_in_func226", false }, - { 227, "", "ig_in_func227", false }, - { 228, "", "ig_in_func228", false }, - { -1, "", "", false } }; - -static const char* out_sel_name(int function) { - const gpio_matrix_t* p; - for (p = gpio_matrix; p->num != -1; ++p) { - if (p->num == function) { - return p->out; - } - } - return ""; -} - -static void show_matrix(Print& out) { - const gpio_matrix_t* p; - for (p = gpio_matrix; p->num != -1; ++p) { - uint32_t in_sel = gpio_in_sel(p->num); - if (in_sel & 0x80) { - out << p->num << " " << p->in << " " << (in_sel & 0x3f); - if (in_sel & 0x40) { - out << " invert"; - } - out << '\n'; - } - } -} - -#include -void gpio_dump(Print& out) { - for (int gpio = 0; gpio < SOC_GPIO_PIN_COUNT; ++gpio) { - gpio_num_t gpio_num = static_cast(gpio); - if (exists(gpio_num)) { - out << gpio_num << " "; - const char* function_name = pin_function_name(gpio_num, gpio_function(gpio_num)); - out << function_name; - if (!strncmp(function_name, "GPIO", 4)) { - if (is_output(gpio_num)) { - out << " O" << output_level(gpio_num); - } - if (is_input(gpio_num)) { - out << " I" << gpio_get_level(gpio_num); - } - } - uint32_t out_sel = gpio_out_sel(gpio_num); - if (out_sel != 256) { - out << " " << out_sel_name(out_sel); - } - // int func = gpio_function(gpio_num); - // if (func) { - // out << " function " << func; - // } - out << '\n'; - } - } - out << "Input Matrix\n"; - show_matrix(out); -} diff --git a/FluidNC/esp32/gpio_dump.cpp b/FluidNC/esp32/gpio_dump.cpp new file mode 100644 index 000000000..caec8ec30 --- /dev/null +++ b/FluidNC/esp32/gpio_dump.cpp @@ -0,0 +1,358 @@ +#include +#include +#include "driver/gpio.h" +#include "hal/gpio_hal.h" +#include +#include "src/Protocol.h" + +// Support functions for gpio_dump +static int exists(gpio_num_t gpio) { + if (gpio == 20) { + // GPIO20 is listed in GPIO_PIN_MUX_REG[] but it is only + // available on the ESP32-PICO-V3 package. + return false; + } + return GPIO_PIN_MUX_REG[gpio]; // Missing GPIOs have 0 entries in this array +} +static int output_level(gpio_num_t gpio) { + if (gpio < 32) { + return REG_READ(GPIO_OUT_REG) & (1 << gpio); + } else { + return REG_READ(GPIO_OUT1_REG) & (1 << (gpio - 32)); + } +} + +static int is_input(gpio_num_t gpio) { + return GET_PERI_REG_MASK(GPIO_PIN_MUX_REG[gpio], FUN_IE); +} +static int is_output(gpio_num_t gpio) { + if (gpio < 32) { + return GET_PERI_REG_MASK(GPIO_ENABLE_REG, 1 << gpio); + } else { + return GET_PERI_REG_MASK(GPIO_ENABLE1_REG, 1 << (gpio - 32)); + } +} +static int gpio_function(gpio_num_t gpio) { + return REG_GET_FIELD(GPIO_PIN_MUX_REG[gpio], MCU_SEL); +} +static uint32_t gpio_out_sel(gpio_num_t gpio) { + return REG_READ(GPIO_FUNC0_OUT_SEL_CFG_REG + (gpio * 4)); +} +static uint32_t gpio_in_sel(int function) { + return REG_READ(GPIO_FUNC0_IN_SEL_CFG_REG + (function * 4)); +} + +// another way to determine available gpios is the array GPIO_PIN_MUX_REG[SOC_GPIO_PIN_COUNT] +// which has 0 in unavailable slots, see soc/gpio_periph.{ch} +std::vector avail_gpios = { 0, 1, 3, 4, 5, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33, 34, 35, 36, 39 }; + +struct pin_mux { + int pinnum; + const char* pinname; + const char* functions[6]; +} const pins[] = { + { 0, "GPIO0", { "GPIO0", "CLK_OUT1", "GPIO0", "-", "-", "EMAC_TX_CLK" } }, + { 1, "U0TXD", { "U0TXD", "CLK_OUT3", "GPIO1", "-", "-", "EMAC_RXD2" } }, + { 2, "GPIO2", { "GPIO2", "HSPIWP", "GPIO2", "HS2_DATA0", "SD_DATA0", "-" } }, + { 3, "U0RXD", { "U0RXD", "CLK_OUT2", "GPIO3", "-", "-", "-" } }, + { 4, "GPIO4", { "GPIO4", "HSPIHD", "GPIO4", "HS2_DATA1", "SD_DATA1", "EMAC_TX_ER" } }, + { 5, "GPIO5", { "GPIO5", "VSPICS0", "GPIO5", "HS1_DATA6", "-", "EMAC_RX_CLK" } }, + { 6, "SD_CLK", { "SD_CLK", "SPICLK", "GPIO6", "HS1_CLK", "U1CTS", "-" } }, + { 7, "SD_DATA_0", { "SD_DATA0", "SPIQ", "GPIO7", "HS1_DATA0", "U2RTS", "-" } }, + { 8, "SD_DATA_1", { "SD_DATA1", "SPID", "GPIO8", "HS1_DATA1", "U2CTS", "-" } }, + { 9, "SD_DATA_2", { "SD_DATA2", "SPIHD", "GPIO9", "HS1_DATA2", "U1RXD", "-" } }, + { 10, "SD_DATA_3", { "SD_DATA3", "SPIWP", "GPIO10", "HS1_DATA3", "U1TXD", "-" } }, + { 11, "SD_CMD", { "SD_CMD", "SPICS0", "GPIO11", "HS1_CMD", "U1RTS", "-" } }, + { 12, "MTDI", { "MTDI", "HSPIQ", "GPIO12", "HS2_DATA2", "SD_DATA2", "EMAC_TXD3" } }, + { 13, "MTCK", { "MTCK", "HSPID", "GPIO13", "HS2_DATA3", "SD_DATA3", "EMAC_RX_ER" } }, + { 14, "MTMS", { "MTMS", "HSPICLK", "GPIO14", "HS2_CLK", "SD_CLK", "EMAC_TXD2" } }, + { 15, "MTDO", { "MTDO", "HSPICS0", "GPIO15", "HS2_CMD", "SD_CMD", "EMAC_RXD3" } }, + { 16, "GPIO16", { "GPIO16", "-", "GPIO16", "HS1_DATA4", "U2RXD", "EMAC_CLK_OUT1" } }, + { 17, "GPIO17", { "GPIO17", "-", "GPIO17", "HS1_DATA5", "U2TXD", "EMAC_CLK_1801" } }, + { 18, "GPIO18", { "GPIO18", "VSPICLK", "GPIO18", "HS1_DATA7", "-", "-" } }, + { 19, "GPIO19", { "GPIO19", "VSPIQ", "GPIO19", "U0CTS", "-", "EMAC_TXD0" } }, + { 21, "GPIO21", { "GPIO21", "VSPIHD", "GPIO21", "-", "-", "EMAC_TX_EN" } }, + { 22, "GPIO22", { "GPIO22", "VSPIWP", "GPIO22", "U0RTS", "-", "EMAC_TXD1" } }, + { 23, "GPIO23", { "GPIO23", "VSPID", "GPIO23", "HS1_STROBE", "-", "-" } }, + { 25, "GPIO25", { "GPIO25", "-", "GPIO25", "-", "-", "EMAC_RXD0" } }, + { 26, "GPIO26", { "GPIO26", "-", "GPIO26", "-", "-", "EMAC_RXD1" } }, + { 27, "GPIO27", { "GPIO27", "-", "GPIO27", "-", "-", "EMAC_RX_DV" } }, + { 32, "32K_XP", { "GPIO32", "-", "GPIO32", "-", "-", "-" } }, + { 33, "32K_XN", { "GPIO33", "-", "GPIO33", "-", "-", "-" } }, + { 34, "VDET_1", { "GPIO34", "-", "GPIO34", "-", "-", "-" } }, + { 35, "VDET_2", { "GPIO35", "-", "GPIO35", "-", "-", "-" } }, + { 36, "SENSOR_VP", { "GPIO36", "-", "GPIO36", "-", "-", "-" } }, + { 37, "SENSOR_CAPP", { "GPIO37", "-", "GPIO37", "-", "-", "-" } }, + { 38, "SENSOR_CAPN", { "GPIO38", "-", "GPIO38", "-", "-", "-" } }, + { 39, "SENSOR_VN", { "GPIO39", "-", "GPIO39", "-", "-", "-" } }, + { -1, "", { "" } }, +}; +const char* pin_function_name(gpio_num_t gpio, int function) { + const pin_mux* p; + for (p = pins; p->pinnum != -1; ++p) { + if (p->pinnum == gpio) { + return p->functions[function]; + } + } + return ""; +} + +struct gpio_matrix_t { + int num; + const char* in; + const char* out; + int iomux; +} const gpio_matrix[] = { { 0, "SPICLK_in", "SPICLK_out", true }, + { 1, "SPIQ_in", "SPIQ_out", true }, + { 2, "SPID_in", "SPID_out", true }, + { 3, "SPIHD_in", "SPIHD_out", true }, + { 4, "SPIWP_in", "SPIWP_out", true }, + { 5, "SPICS0_in", "SPICS0_out", true }, + { 6, "SPICS1_in", "SPICS1_out", false }, + { 7, "SPICS2_in", "SPICS2_out", false }, + { 8, "HSPICLK_in", "HSPICLK_out", true }, + { 9, "HSPIQ_in", "HSPIQ_out", true }, + { 10, "HSPID_in", "HSPID_out", true }, + { 11, "HSPICS0_in", "HSPICS0_out", true }, + { 12, "HSPIHD_in", "HSPIHD_out", true }, + { 13, "HSPIWP_in", "HSPIWP_out", true }, + { 14, "U0RXD_in", "U0TXD_out", true }, + { 15, "U0CTS_in", "U0RTS_out", true }, + { 16, "U0DSR_in", "U0DTR_out", false }, + { 17, "U1RXD_in", "U1TXD_out", true }, + { 18, "U1CTS_in", "U1RTS_out", true }, + { 23, "I2S0O_BCK_in", "I2S0O_BCK_out", false }, + { 24, "I2S1O_BCK_in", "I2S1O_BCK_out", false }, + { 25, "I2S0O_WS_in", "I2S0O_WS_out", false }, + { 26, "I2S1O_WS_in", "I2S1O_WS_out", false }, + { 27, "I2S0I_BCK_in", "I2S0I_BCK_out", false }, + { 28, "I2S0I_WS_in", "I2S0I_WS_out", false }, + { 29, "I2CEXT0_SCL_in", "I2CEXT0_SCL_out", false }, + { 30, "I2CEXT0_SDA_in", "I2CEXT0_SDA_out", false }, + { 31, "pwm0_sync0_in", "sdio_tohost_int_out", false }, + { 32, "pwm0_sync1_in", "pwm0_out0a", false }, + { 33, "pwm0_sync2_in", "pwm0_out0b", false }, + { 34, "pwm0_f0_in", "pwm0_out1a", false }, + { 35, "pwm0_f1_in", "pwm0_out1b", false }, + { 36, "pwm0_f2_in", "pwm0_out2a", false }, + { 37, "", "pwm0_out2b", false }, + { 39, "pcnt_sig_ch0_in0", "", false }, + { 40, "pcnt_sig_ch1_in0", "", false }, + { 41, "pcnt_ctrl_ch0_in0", "", false }, + { 42, "pcnt_ctrl_ch1_in0", "", false }, + { 43, "pcnt_sig_ch0_in1", "", false }, + { 44, "pcnt_sig_ch1_in1", "", false }, + { 45, "pcnt_ctrl_ch0_in1", "", false }, + { 46, "pcnt_ctrl_ch1_in1", "", false }, + { 47, "pcnt_sig_ch0_in2", "", false }, + { 48, "pcnt_sig_ch1_in2", "", false }, + { 49, "pcnt_ctrl_ch0_in2", "", false }, + { 50, "pcnt_ctrl_ch1_in2", "", false }, + { 51, "pcnt_sig_ch0_in3", "", false }, + { 52, "pcnt_sig_ch1_in3", "", false }, + { 53, "pcnt_ctrl_ch0_in3", "", false }, + { 54, "pcnt_ctrl_ch1_in3", "", false }, + { 55, "pcnt_sig_ch0_in4", "", false }, + { 56, "pcnt_sig_ch1_in4", "", false }, + { 57, "pcnt_ctrl_ch0_in4", "", false }, + { 58, "pcnt_ctrl_ch1_in4", "", false }, + { 61, "HSPICS1_in", "HSPICS1_out", false }, + { 62, "HSPICS2_in", "HSPICS2_out", false }, + { 63, "VSPICLK_in", "VSPICLK_out_mux", true }, + { 64, "VSPIQ_in", "VSPIQ_out", true }, + { 65, "VSPID_in", "VSPID_out", true }, + { 66, "VSPIHD_in", "VSPIHD_out", true }, + { 67, "VSPIWP_in", "VSPIWP_out", true }, + { 68, "VSPICS0_in", "VSPICS0_out", true }, + { 69, "VSPICS1_in", "VSPICS1_out", false }, + { 70, "VSPICS2_in", "VSPICS2_out", false }, + { 71, "pcnt_sig_ch0_in5", "ledc_hs_sig_out0", false }, + { 72, "pcnt_sig_ch1_in5", "ledc_hs_sig_out1", false }, + { 73, "pcnt_ctrl_ch0_in5", "ledc_hs_sig_out2", false }, + { 74, "pcnt_ctrl_ch1_in5", "ledc_hs_sig_out3", false }, + { 75, "pcnt_sig_ch0_in6", "ledc_hs_sig_out4", false }, + { 76, "pcnt_sig_ch1_in6", "ledc_hs_sig_out5", false }, + { 77, "pcnt_ctrl_ch0_in6", "ledc_hs_sig_out6", false }, + { 78, "pcnt_ctrl_ch1_in6", "ledc_hs_sig_out7", false }, + { 79, "pcnt_sig_ch0_in7", "ledc_ls_sig_out0", false }, + { 80, "pcnt_sig_ch1_in7", "ledc_ls_sig_out1", false }, + { 81, "pcnt_ctrl_ch0_in7", "ledc_ls_sig_out2", false }, + { 82, "pcnt_ctrl_ch1_in7", "ledc_ls_sig_out3", false }, + { 83, "rmt_sig_in0", "ledc_ls_sig_out4", false }, + { 84, "rmt_sig_in1", "ledc_ls_sig_out5", false }, + { 85, "rmt_sig_in2", "ledc_ls_sig_out6", false }, + { 86, "rmt_sig_in3", "ledc_ls_sig_out7", false }, + { 87, "rmt_sig_in4", "rmtt_sig_out0", false }, + { 88, "rmt_sig_in5", "rmtt_sig_out1", false }, + { 89, "rmt_sig_in6", "rmtt_sig_out2", false }, + { 90, "rmt_sig_in7", "rmtt_sig_out3", false }, + { 91, "", "rmtt_sig_out4", false }, + { 92, "", "rmtt_sig_out5", false }, + { 93, "", "rmtt_sig_out6", false }, + { 94, "", "rmtt_sig_out7", false }, + { 95, "I2CEXT1_SCL_in", "I2CEXT1_SCL_out", false }, + { 96, "I2CEXT1_SDA_in", "I2CEXT1_SDA_out", false }, + { 97, "host_card_detect_n_1", "host_ccmd_od_pullup_en_n", false }, + { 98, "host_card_detect_n_2", "host_rst_n_1", false }, + { 99, "host_card_write_prt_1", "host_rst_n_2", false }, + { 100, "host_card_write_prt_2", "gpio_sd0_out", false }, + { 101, "host_card_int_n_1", "gpio_sd1_out", false }, + { 102, "host_card_int_n_2", "gpio_sd2_out", false }, + { 103, "pwm1_sync0_in", "gpio_sd3_out", false }, + { 104, "pwm1_sync1_in", "gpio_sd4_out", false }, + { 105, "pwm1_sync2_in", "gpio_sd5_out", false }, + { 106, "pwm1_f0_in", "gpio_sd6_out", false }, + { 107, "pwm1_f1_in", "gpio_sd7_out", false }, + { 108, "pwm1_f2_in", "pwm1_out0a", false }, + { 109, "pwm0_cap0_in", "pwm1_out0b", false }, + { 110, "pwm0_cap1_in", "pwm1_out1a", false }, + { 111, "pwm0_cap2_in", "pwm1_out1b", false }, + { 112, "pwm1_cap0_in", "pwm1_out2a", false }, + { 113, "pwm1_cap1_in", "pwm1_out2b", false }, + { 114, "pwm1_cap2_in", "", false }, + { 115, "", "", false }, + { 116, "", "", false }, + { 117, "", "", false }, + { 118, "", "", false }, + { 119, "", "", false }, + { 120, "", "", false }, + { 121, "", "", false }, + { 122, "", "", false }, + { 123, "", "", false }, + { 124, "", "", false }, + { 140, "I2S0I_DATA_in0", "I2S0O_DATA_out0", false }, + { 141, "I2S0I_DATA_in1", "I2S0O_DATA_out1", false }, + { 142, "I2S0I_DATA_in2", "I2S0O_DATA_out2", false }, + { 143, "I2S0I_DATA_in3", "I2S0O_DATA_out3", false }, + { 144, "I2S0I_DATA_in4", "I2S0O_DATA_out4", false }, + { 145, "I2S0I_DATA_in5", "I2S0O_DATA_out5", false }, + { 146, "I2S0I_DATA_in6", "I2S0O_DATA_out6", false }, + { 147, "I2S0I_DATA_in7", "I2S0O_DATA_out7", false }, + { 148, "I2S0I_DATA_in8", "I2S0O_DATA_out8", false }, + { 149, "I2S0I_DATA_in9", "I2S0O_DATA_out9", false }, + { 150, "I2S0I_DATA_in10", "I2S0O_DATA_out10", false }, + { 151, "I2S0I_DATA_in11", "I2S0O_DATA_out11", false }, + { 152, "I2S0I_DATA_in12", "I2S0O_DATA_out12", false }, + { 153, "I2S0I_DATA_in13", "I2S0O_DATA_out13", false }, + { 154, "I2S0I_DATA_in14", "I2S0O_DATA_out14", false }, + { 155, "I2S0I_DATA_in15", "I2S0O_DATA_out15", false }, + { 156, "", "I2S0O_DATA_out16", false }, + { 157, "", "I2S0O_DATA_out17", false }, + { 158, "", "I2S0O_DATA_out18", false }, + { 159, "", "I2S0O_DATA_out19", false }, + { 160, "", "I2S0O_DATA_out20", false }, + { 161, "", "I2S0O_DATA_out21", false }, + { 162, "", "I2S0O_DATA_out22", false }, + { 163, "", "I2S0O_DATA_out23", false }, + { 164, "I2S1I_BCK_in", "I2S1I_BCK_out", false }, + { 165, "I2S1I_WS_in", "I2S1I_WS_out", false }, + { 166, "I2S1I_DATA_in0", "I2S1O_DATA_out0", false }, + { 167, "I2S1I_DATA_in1", "I2S1O_DATA_out1", false }, + { 168, "I2S1I_DATA_in2", "I2S1O_DATA_out2", false }, + { 169, "I2S1I_DATA_in3", "I2S1O_DATA_out3", false }, + { 170, "I2S1I_DATA_in4", "I2S1O_DATA_out4", false }, + { 171, "I2S1I_DATA_in5", "I2S1O_DATA_out5", false }, + { 172, "I2S1I_DATA_in6", "I2S1O_DATA_out6", false }, + { 173, "I2S1I_DATA_in7", "I2S1O_DATA_out7", false }, + { 174, "I2S1I_DATA_in8", "I2S1O_DATA_out8", false }, + { 175, "I2S1I_DATA_in9", "I2S1O_DATA_out9", false }, + { 176, "I2S1I_DATA_in10", "I2S1O_DATA_out10", false }, + { 177, "I2S1I_DATA_in11", "I2S1O_DATA_out11", false }, + { 178, "I2S1I_DATA_in12", "I2S1O_DATA_out12", false }, + { 179, "I2S1I_DATA_in13", "I2S1O_DATA_out13", false }, + { 180, "I2S1I_DATA_in14", "I2S1O_DATA_out14", false }, + { 181, "I2S1I_DATA_in15", "I2S1O_DATA_out15", false }, + { 182, "", "I2S1O_DATA_out16", false }, + { 183, "", "I2S1O_DATA_out17", false }, + { 184, "", "I2S1O_DATA_out18", false }, + { 185, "", "I2S1O_DATA_out19", false }, + { 186, "", "I2S1O_DATA_out20", false }, + { 187, "", "I2S1O_DATA_out21", false }, + { 188, "", "I2S1O_DATA_out22", false }, + { 189, "", "I2S1O_DATA_out23", false }, + { 190, "I2S0I_H_SYNC", "", false }, + { 191, "I2S0I_V_SYNC", "", false }, + { 192, "I2S0I_H_ENABLE", "", false }, + { 193, "I2S1I_H_SYNC", "", false }, + { 194, "I2S1I_V_SYNC", "", false }, + { 195, "I2S1I_H_ENABLE", "", false }, + { 196, "", "", false }, + { 197, "", "", false }, + { 198, "U2RXD_in", "U2TXD_out", true }, + { 199, "U2CTS_in", "U2RTS_out", true }, + { 200, "emac_mdc_i", "emac_mdc_o", false }, + { 201, "emac_mdi_i", "emac_mdo_o", false }, + { 202, "emac_crs_i", "emac_crs_o", false }, + { 203, "emac_col_i", "emac_col_o", false }, + { 204, "pcmfsync_in", "bt_audio0_irq", false }, + { 205, "pcmclk_in", "bt_audio1_irq", false }, + { 206, "pcmdin", "bt_audio2_irq", false }, + { 207, "", "le_audio0_irq", false }, + { 208, "", "le_audio1_irq", false }, + { 209, "", "le_audio2_irq", false }, + { 210, "", "cmfsync_out", false }, + { 211, "", "cmclk_out", false }, + { 212, "", "cmdout", false }, + { 213, "", "le_audio_sync0_p", false }, + { 214, "", "le_audio_sync1_p", false }, + { 215, "", "le_audio_sync2_p", false }, + { 224, "", "ig_in_func224", false }, + { 225, "", "ig_in_func225", false }, + { 226, "", "ig_in_func226", false }, + { 227, "", "ig_in_func227", false }, + { 228, "", "ig_in_func228", false }, + { -1, "", "", false } }; + +static const char* out_sel_name(int function) { + const gpio_matrix_t* p; + for (p = gpio_matrix; p->num != -1; ++p) { + if (p->num == function) { + return p->out; + } + } + return ""; +} + +static void show_matrix(Print& out) { + const gpio_matrix_t* p; + for (p = gpio_matrix; p->num != -1; ++p) { + uint32_t in_sel = gpio_in_sel(p->num); + if (in_sel & 0x80) { + out << p->num << " " << p->in << " " << (in_sel & 0x3f); + if (in_sel & 0x40) { + out << " invert"; + } + out << '\n'; + } + } +} + +void gpio_dump(Print& out) { + for (int gpio = 0; gpio < SOC_GPIO_PIN_COUNT; ++gpio) { + gpio_num_t gpio_num = static_cast(gpio); + if (exists(gpio_num)) { + out << gpio_num << " "; + const char* function_name = pin_function_name(gpio_num, gpio_function(gpio_num)); + out << function_name; + if (!strncmp(function_name, "GPIO", 4)) { + if (is_output(gpio_num)) { + out << " O" << output_level(gpio_num); + } + if (is_input(gpio_num)) { + out << " I" << gpio_get_level(gpio_num); + } + } + uint32_t out_sel = gpio_out_sel(gpio_num); + if (out_sel != 256) { + out << " " << out_sel_name(out_sel); + } + // int func = gpio_function(gpio_num); + // if (func) { + // out << " function " << func; + // } + out << '\n'; + } + } + out << "Input Matrix\n"; + show_matrix(out); +} diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c new file mode 100644 index 000000000..a50644b60 --- /dev/null +++ b/FluidNC/esp32/i2s_engine.c @@ -0,0 +1,499 @@ +// Copyright (c) 2024 - Mitch Bradley +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +// Stepping engine that uses the I2S FIFO + +#include "Driver/step_engine.h" +#include "Driver/i2s_out.h" +#include "hal/i2s_hal.h" + +#include + +#include "Driver/delay_usecs.h" // delay_us() + +#include // IRAM_ATTR + +#include +// #include + +#include +#include +#include +#include +#include "Driver/fluidnc_gpio.h" + +/* 16-bit mode: 1000000 usec / ((160000000 Hz) / 10 / 2) x 16 bit/pulse x 2(stereo) = 4 usec/pulse */ +/* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ +const uint32_t I2S_OUT_USEC_PER_PULSE = 2; + +// The library routines are not in IRAM so they can crash when called from FLASH +// The GCC intrinsic versions which are prefixed with __ are compiled inline +#define USE_INLINE_ATOMIC + +#ifdef USE_INLINE_ATOMIC +# define MEMORY_MODEL_FETCH __ATOMIC_RELAXED +# define MEMORY_MODEL_STORE __ATOMIC_RELAXED +# define ATOMIC_LOAD(var) __atomic_load_n(var, MEMORY_MODEL_FETCH) +# define ATOMIC_STORE(var, val) __atomic_store_n(var, val, MEMORY_MODEL_STORE) +# define ATOMIC_FETCH_AND(var, val) __atomic_fetch_and(var, val, MEMORY_MODEL_FETCH) +# define ATOMIC_FETCH_OR(var, val) __atomic_fetch_or(var, val, MEMORY_MODEL_FETCH) +static uint32_t i2s_out_port_data = 0; +#else +# include +# define ATOMIC_LOAD(var) atomic_load(var) +# define ATOMIC_STORE(var, val) atomic_store(var, val) +# define ATOMIC_FETCH_AND(var, val) atomic_fetch_and(var, val) +# define ATOMIC_FETCH_OR(var, val) atomic_fetch_or(var, val) +static std::atomic i2s_out_port_data = ATOMIC_VAR_INIT(0); + +#endif + +const int I2S_SAMPLE_SIZE = 4; /* 4 bytes, 32 bits per sample */ + +// inner lock +static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; +#define I2S_OUT_ENTER_CRITICAL() \ + do { \ + if (xPortInIsrContext()) { \ + portENTER_CRITICAL_ISR(&i2s_out_spinlock); \ + } else { \ + portENTER_CRITICAL(&i2s_out_spinlock); \ + } \ + } while (0) +#define I2S_OUT_EXIT_CRITICAL() \ + do { \ + if (xPortInIsrContext()) { \ + portEXIT_CRITICAL_ISR(&i2s_out_spinlock); \ + } else { \ + portEXIT_CRITICAL(&i2s_out_spinlock); \ + } \ + } while (0) +#define I2S_OUT_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_spinlock) +#define I2S_OUT_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_spinlock) + +static int i2s_out_initialized = 0; + +static pinnum_t i2s_out_ws_pin = 255; +static pinnum_t i2s_out_bck_pin = 255; +static pinnum_t i2s_out_data_pin = 255; + +// outer lock +static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; +#define I2S_OUT_PULSER_ENTER_CRITICAL() \ + do { \ + if (xPortInIsrContext()) { \ + portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ + } else { \ + portENTER_CRITICAL(&i2s_out_pulser_spinlock); \ + } \ + } while (0) +#define I2S_OUT_PULSER_EXIT_CRITICAL() \ + do { \ + if (xPortInIsrContext()) { \ + portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ + } else { \ + portEXIT_CRITICAL(&i2s_out_pulser_spinlock); \ + } \ + } while (0) +#define I2S_OUT_PULSER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock) +#define I2S_OUT_PULSER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock) + +#if I2S_OUT_NUM_BITS == 16 +# define DATA_SHIFT 16 +#else +# define DATA_SHIFT 0 +#endif + +// +// Internal functions +// +void IRAM_ATTR i2s_out_push_fifo(int count) { +#if 0 + uint32_t portData = ATOMIC_LOAD(&i2s_out_port_data) << DATA_SHIFT; +#else + uint32_t portData = i2s_out_port_data << DATA_SHIFT; +#endif + for (int i = 0; i < count; i++) { + I2S0.fifo_wr = portData; + } +} + +static inline void i2s_out_reset_tx_rx() { + i2s_ll_tx_reset(&I2S0); + i2s_ll_rx_reset(&I2S0); +} + +static inline void i2s_out_reset_fifo_without_lock() { + i2s_ll_tx_reset_fifo(&I2S0); + i2s_ll_rx_reset_fifo(&I2S0); +} + +static int i2s_out_gpio_attach(pinnum_t ws, pinnum_t bck, pinnum_t data) { + // Route the i2s pins to the appropriate GPIO + gpio_route(data, I2S0O_DATA_OUT23_IDX); + gpio_route(bck, I2S0O_BCK_OUT_IDX); + gpio_route(ws, I2S0O_WS_OUT_IDX); + return 0; +} + +const int I2S_OUT_DETACH_PORT_IDX = 0x100; + +static int i2s_out_gpio_detach(pinnum_t ws, pinnum_t bck, pinnum_t data) { + // Route the i2s pins to the appropriate GPIO + gpio_route(ws, I2S_OUT_DETACH_PORT_IDX); + gpio_route(bck, I2S_OUT_DETACH_PORT_IDX); + gpio_route(data, I2S_OUT_DETACH_PORT_IDX); + return 0; +} + +static int i2s_out_gpio_shiftout(uint32_t port_data) { + gpio_write(i2s_out_ws_pin, 0); + for (int i = 0; i < I2S_OUT_NUM_BITS; i++) { + gpio_write(i2s_out_data_pin, !!(port_data & (1 << (I2S_OUT_NUM_BITS - 1 - i)))); + gpio_write(i2s_out_bck_pin, 1); + gpio_write(i2s_out_bck_pin, 0); + } + gpio_write(i2s_out_ws_pin, 1); // Latch + return 0; +} + +static int i2s_out_stop() { + I2S_OUT_ENTER_CRITICAL(); + + // stop TX module + i2s_ll_tx_stop(&I2S0); + + // Force WS to LOW before detach + // This operation prevents unintended WS edge trigger when detach + gpio_write(i2s_out_ws_pin, 0); + + // Now, detach GPIO pin from I2S + i2s_out_gpio_detach(i2s_out_ws_pin, i2s_out_bck_pin, i2s_out_data_pin); + + // Force BCK to LOW + // After the TX module is stopped, BCK always seems to be in LOW. + // However, I'm going to do it manually to ensure the BCK's LOW. + gpio_write(i2s_out_bck_pin, 0); + + // Transmit recovery data to 74HC595 + uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value + i2s_out_gpio_shiftout(port_data); + +#if 0 + //clear pending interrupt + i2s_ll_clear_intr_status(&I2S0, i2s_ll_get_intr_status(&I2S0)); +#endif + + I2S_OUT_EXIT_CRITICAL(); + return 0; +} + +static int i2s_out_start() { + if (!i2s_out_initialized) { + return -1; + } + + I2S_OUT_ENTER_CRITICAL(); + // Transmit recovery data to 74HC595 + uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value + i2s_out_gpio_shiftout(port_data); + + // Attach I2S to specified GPIO pin + i2s_out_gpio_attach(i2s_out_ws_pin, i2s_out_bck_pin, i2s_out_data_pin); + + // reset TX/RX module + // reset FIFO + i2s_out_reset_tx_rx(); + i2s_out_reset_fifo_without_lock(); + + i2s_ll_tx_set_chan_mod(&I2S0, I2S_CHANNEL_FMT_ONLY_LEFT); + i2s_ll_tx_stop_on_fifo_empty(&I2S0, true); + +#if 0 + i2s_ll_clear_intr_status(&I2S0, 0xFFFFFFFF); +#endif + + i2s_ll_tx_start(&I2S0); + + // Wait for the first FIFO data to prevent the unintentional generation of 0 data + delay_us(20); + i2s_ll_tx_stop_on_fifo_empty(&I2S0, false); + + I2S_OUT_EXIT_CRITICAL(); + + return 0; +} + +// +// External funtions +// +void i2s_out_delay() { + I2S_OUT_PULSER_ENTER_CRITICAL(); + // Depending on the timing, it may not be reflected immediately, + // so wait twice as long just in case. + delay_us(I2S_OUT_USEC_PER_PULSE * 2); + I2S_OUT_PULSER_EXIT_CRITICAL(); +} + +void IRAM_ATTR i2s_out_write(pinnum_t pin, uint8_t val) { + uint32_t bit = 1 << pin; + if (val) { + ATOMIC_FETCH_OR(&i2s_out_port_data, bit); + } else { + ATOMIC_FETCH_AND(&i2s_out_port_data, ~bit); + } +} + +uint8_t i2s_out_read(pinnum_t pin) { + uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); + return !!(port_data & (1 << pin)); +} + +// +// Initialize function (external function) +// +int i2s_out_init(i2s_out_init_t* init_param) { + if (i2s_out_initialized) { + // already initialized + return -1; + } + + ATOMIC_STORE(&i2s_out_port_data, init_param->init_val); + + // To make sure hardware is enabled before any hardware register operations. + periph_module_reset(PERIPH_I2S0_MODULE); + periph_module_enable(PERIPH_I2S0_MODULE); + + // Route the i2s pins to the appropriate GPIO + i2s_out_gpio_attach(init_param->ws_pin, init_param->bck_pin, init_param->data_pin); + + /** + * Each i2s transfer will take + * fpll = PLL_D2_CLK -- clka_en = 0 + * + * fi2s = fpll / N + b/a -- N + b/a = clkm_div_num + * fi2s = 160MHz / 2 + * fi2s = 80MHz + * + * fbclk = fi2s / M -- M = tx_bck_div_num + * fbclk = 80MHz / 2 + * fbclk = 40MHz + * + * fwclk = fbclk / 32 + * + * for fwclk = 250kHz(16-bit: 4µS pulse time), 125kHz(32-bit: 8μS pulse time) + * N = 10, b/a = 0 + * M = 2 + * for fwclk = 500kHz(16-bit: 2µS pulse time), 250kHz(32-bit: 4μS pulse time) + * N = 5, b/a = 0 + * M = 2 + * for fwclk = 1000kHz(16-bit: 1µS pulse time), 500kHz(32-bit: 2μS pulse time) + * N = 2, b/a = 2/1 (N + b/a = 2.5) + * M = 2 + */ + + // stop i2s + i2s_ll_tx_stop_link(&I2S0); + i2s_ll_tx_stop(&I2S0); + + // i2s_param_config + + // configure I2S data port interface. + + i2s_out_reset_fifo_without_lock(); + + i2s_ll_enable_lcd(&I2S0, false); + i2s_ll_enable_camera(&I2S0, false); +#ifdef SOC_I2S_SUPPORTS_PDM_TX + i2s_ll_tx_enable_pdm(&I2S0, false); + i2s_ll_rx_enable_pdm(&I2S0, false); +#endif + + i2s_ll_enable_dma(&I2S0, false); + + i2s_ll_tx_set_chan_mod(&I2S0, I2S_CHANNEL_FMT_ONLY_LEFT); + +#if I2S_OUT_NUM_BITS == 16 + i2s_ll_tx_set_sample_bit(&I2S0, I2S_BITS_PER_SAMPLE_16BIT, I2S_BITS_PER_SAMPLE_16BIT); + i2s_ll_rx_set_sample_bit(&I2S0, I2S_BITS_PER_SAMPLE_16BIT, I2S_BITS_PER_SAMPLE_16BIT); +#else + i2s_ll_tx_set_sample_bit(&I2S0, I2S_BITS_PER_SAMPLE_32BIT, I2S_BITS_PER_SAMPLE_32BIT); + i2s_ll_rx_set_sample_bit(&I2S0, I2S_BITS_PER_SAMPLE_32BIT, I2S_BITS_PER_SAMPLE_32BIT); + i2s_ll_tx_enable_mono_mode(&I2S0, true); + i2s_ll_rx_enable_mono_mode(&I2S0, true); + // Data width is 32-bit. Forgetting this setting will result in a 16-bit transfer. +#endif + // I2S0.conf.tx_mono = 0; // Set this bit to enable transmitter’s mono mode in PCM standard mode. + + i2s_ll_rx_set_chan_mod(&I2S0, 1); + // i2s_ll_rx_set_chan_mod(&I2S0, I2S_CHANNEL_FMT_ALL_LEFT, false); + // I2S0.conf.rx_mono = 0; + + i2s_ll_enable_dma(&I2S0, false); // FIFO is not connected to DMA + i2s_ll_tx_stop(&I2S0); + i2s_ll_rx_stop(&I2S0); + + i2s_ll_tx_enable_msb_right(&I2S0, true); // Place right-channel data at the MSB in the transmit FIFO. + i2s_ll_tx_enable_right_first(&I2S0, false); // Send the left-channel data first + + i2s_ll_tx_set_slave_mod(&I2S0, false); // Master + i2s_ll_tx_force_enable_fifo_mod(&I2S0, true); +#ifdef SOC_I2S_SUPPORTS_PDM_RX + i2s_ll_rx_enable_pdm(&I2S0, false); +#endif +#ifdef SOC_I2S_SUPPORTS_PDM_TX + i2s_ll_tx_enable_pdm(&I2S0, false); +#endif + + // I2S_COMM_FORMAT_I2S_LSB + i2s_ll_tx_set_ws_width(&I2S0, 0); // PCM standard mode. + i2s_ll_rx_set_ws_width(&I2S0, 0); // PCM standard mode. + i2s_ll_tx_enable_msb_shift(&I2S0, false); // Do not use the Philips standard to avoid bit-shifting + i2s_ll_rx_enable_msb_shift(&I2S0, false); // Do not use the Philips standard to avoid bit-shifting + + // i2s_set_clk + + // set clock (fi2s) 160MHz / 5 +#ifdef CONFIG_IDF_TARGET_ESP32 + i2s_ll_tx_clk_set_src(&I2S0, I2S_CLK_D2CLK); +#endif + // N + b/a = 0 +#if I2S_OUT_NUM_BITS == 16 + // N = 10 + uint16_t mclk_div = 10; +#else + uint16_t mclk_div = 3; + // N = 5 + // 5 could be changed to 2 to make I2SO pulse at 312.5 kHZ instead of 125 kHz, but doing so would + // require some changes to deal with pulse lengths that are not an integral number of microseconds. +#endif + i2s_ll_mclk_div_t first_div = { 2, 3, 47 }; // { N, b, a } + i2s_ll_tx_set_clk(&I2S0, &first_div); + + volatile void* ptr = &I2S0; + uint32_t value; + + delay_us(20); + value = *(uint32_t*)(ptr + 0xac); + printf("Clkreg AENA %d b %d a %d n %d\n", (value >> 21) & 1, (value >> 14) & 0x3f, (value >> 8) & 0x3f, value & 0xff); + + i2s_ll_mclk_div_t div = { 2, 32, 16 }; // b/a = 0.5 + i2s_ll_tx_set_clk(&I2S0, &div); + + value = *(uint32_t*)(ptr + 0xac); + printf("Clkreg AENA %d b %d a %d n %d\n", (value >> 21) & 1, (value >> 14) & 0x3f, (value >> 8) & 0x3f, value & 0xff); + + // Bit clock configuration bit in transmitter mode. + // fbck = fi2s / tx_bck_div_num = (160 MHz / 5) / 2 = 16 MHz + i2s_ll_tx_set_bck_div_num(&I2S0, 2); + i2s_ll_rx_set_bck_div_num(&I2S0, 2); + + // Remember GPIO pin numbers + i2s_out_ws_pin = init_param->ws_pin; + i2s_out_bck_pin = init_param->bck_pin; + i2s_out_data_pin = init_param->data_pin; + i2s_out_initialized = 1; + + // Start the I2S peripheral + i2s_out_start(); + + return 0; +} + +static uint32_t _pulse_counts = 2; +static uint32_t _dir_delay_us; + +// Convert the delays from microseconds to a number of I2S frame +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us) { + printf("Pus %d\n", pulse_us); + if (pulse_us < I2S_OUT_USEC_PER_PULSE) { + pulse_us = I2S_OUT_USEC_PER_PULSE; + } + if (pulse_us > I2S_MAX_USEC_PER_PULSE) { + pulse_us = I2S_MAX_USEC_PER_PULSE; + } + _dir_delay_us = dir_delay_us; + printf("Pus2 %d Uspp %d\n", pulse_us, I2S_OUT_USEC_PER_PULSE); + _pulse_counts = (pulse_us + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; + + printf("PC %d plen %d\n", _pulse_counts, _pulse_counts * I2S_OUT_USEC_PER_PULSE); + return _pulse_counts * I2S_OUT_USEC_PER_PULSE; +} + +static int init_step_pin(int step_pin, int step_invert) { + return step_pin; +} + +// This modifies a memory variable that contains the desired +// pin states. Later, that variable will be transferred to +// the I2S FIFO to change all the affected pins at once. +static IRAM_ATTR void set_dir_pin(int pin, int level) { + i2s_out_write(pin, level); +} + +uint32_t new_port_data; + +static IRAM_ATTR void start_step() { + new_port_data = i2s_out_port_data; +} + +static IRAM_ATTR void set_step_pin(int pin, int level) { + uint32_t bit = 1 << pin; + if (level) { + new_port_data |= bit; + } else { + new_port_data &= ~bit; + } +} + +// For direction changes, we push one sample to the FIFO +// and busy-wait for the delay. If the delay is short enough, +// it might be possible to use the same multiple-sample trick +// that we use for step pulses, but the optimizaton might not +// be worthwhile since direction changes are infrequent. +static IRAM_ATTR void finish_dir() { + i2s_out_push_fifo(1); + delay_us(_dir_delay_us); +} + +// After all the desired values have been set with set_pin(), +// push _pulse_counts copies of the memory variable to the +// I2S FIFO, thus creating a pulse of the desired length. +static IRAM_ATTR void finish_step() { + for (int i = 0; i < _pulse_counts; i++) { + I2S0.fifo_wr = new_port_data; + } + for (int i = 0; i < _pulse_counts; i++) { + I2S0.fifo_wr = i2s_out_port_data; + } +} + +static IRAM_ATTR int start_unstep() { +#if 1 + return 1; +#else + return 0; +#endif +} + +static uint32_t max_pulses_per_sec() { + printf("pulse_counts %d, rate %d\n", _pulse_counts, 1000000 / (2 * _pulse_counts * I2S_OUT_USEC_PER_PULSE)); + return 1000000 / (2 * _pulse_counts * I2S_OUT_USEC_PER_PULSE); +} + +// clang-format off +step_engine_t engine = { + "I2S", + init_engine, + init_step_pin, + set_dir_pin, + finish_dir, + start_step, + set_step_pin, + finish_step, + start_unstep, + finish_step, // finish_step and finish_unstep are the same + max_pulses_per_sec +}; + +REGISTER_STEP_ENGINE(I2S, &engine); diff --git a/FluidNC/esp32/rmt_engine.c b/FluidNC/esp32/rmt_engine.c new file mode 100644 index 000000000..19d65f09c --- /dev/null +++ b/FluidNC/esp32/rmt_engine.c @@ -0,0 +1,133 @@ +// Copyright (c) 2024 - Mitch Bradley +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +// Stepping engine that uses the ESP32 RMT hardware to time step pulses, thus avoiding +// the need to wait for the end of step pulses. + +#include "Driver/step_engine.h" +#include "Driver/fluidnc_gpio.h" +#include +#include +#include // IRAM_ATTR + +static uint32_t _pulse_delay_us; +static uint32_t _dir_delay_us; + +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us) { + _dir_delay_us = dir_delay_us; + _pulse_delay_us = pulse_delay_us; + return _pulse_delay_us; +} + +// Allocate an RMT channel and attach the step_pin GPIO to it, +// setting the timing according to dir_delay_us and pulse_delay_us. +// Return the index of that RMT channel which will be presented to +// set_step_pin() later. +static int init_step_pin(int step_pin, int step_inverted) { + static rmt_channel_t next_RMT_chan_num = RMT_CHANNEL_0; + if (next_RMT_chan_num == RMT_CHANNEL_MAX) { + return -1; + } + rmt_channel_t rmt_chan_num = next_RMT_chan_num; + next_RMT_chan_num = (rmt_channel_t)((int)(next_RMT_chan_num) + 1); + + rmt_config_t rmtConfig = { .rmt_mode = RMT_MODE_TX, + .channel = rmt_chan_num, + .gpio_num = (gpio_num_t)step_pin, + .clk_div = 20, + .mem_block_num = 2, + .flags = 0, + .tx_config = { + .carrier_freq_hz = 0, + .carrier_level = RMT_CARRIER_LEVEL_LOW, + .idle_level = step_inverted ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW, + .carrier_duty_percent = 50, +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + .loop_count = 1, +#endif + .carrier_en = false, + .loop_en = false, + .idle_output_en = true, + } }; + + rmt_item32_t rmtItem[2]; + rmtItem[0].duration0 = _dir_delay_us ? _dir_delay_us * 4 : 1; + rmtItem[0].duration1 = _pulse_delay_us * 4; + rmtItem[1].duration0 = 0; + rmtItem[1].duration1 = 0; + + rmtItem[0].level0 = rmtConfig.tx_config.idle_level; + rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; + rmt_config(&rmtConfig); + rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); + return (int)rmt_chan_num; +} + +// The direction pin is a GPIO that is accessed in the usual way +static IRAM_ATTR void set_dir_pin(int pin, int level) { + gpio_write(pin, level); +} + +// The direction delay is handled by the RMT pulser +static IRAM_ATTR void finish_dir() {} + +static IRAM_ATTR void start_step() {} + +// Restart the RMT which has already been configured +// for the desired pulse length, polarity, and direction delay +static IRAM_ATTR void set_step_pin(int pin, int level) { +#ifdef CONFIG_IDF_TARGET_ESP32 + RMT.conf_ch[pin].conf1.mem_rd_rst = 1; + RMT.conf_ch[pin].conf1.mem_rd_rst = 0; + RMT.conf_ch[pin].conf1.tx_start = 1; +#endif +#ifdef CONFIG_IDF_TARGET_ESP32S3 + RMT.chnconf0[pin].mem_rd_rst_n = 1; + RMT.chnconf0[pin].mem_rd_rst_n = 0; + RMT.chnconf0[pin].tx_start_n = 1; +#endif +} + +// This is a noop because the RMT channels do everything +static IRAM_ATTR void finish_step() {} + +// This is a noop because the RMT channels take care +// of the pulse trailing edges. +// Return 1 (true) to tell Stepping.cpp that it can +// skip the rest of the step pin deassertion process +static IRAM_ATTR int start_unstep() { + return 1; +} + +// This is a noop and will not be called since start_unstep() +// returned true +static IRAM_ATTR void finish_unstep() {} + +// Possible speedup: If the direction delay were done explicitly +// instead of baking it into the RMT timing, we might be able to +// get more pulses per second, since direction changes are infrequent +// and thus do not need to be applied to every pulse +static uint32_t max_pulses_per_sec() { + uint32_t pps = 1000000 / (2 * _pulse_delay_us + _dir_delay_us); + if (pps > 80000) { // Based on testing + pps = 80000; + } + return pps; +} + +// clang-format off +static step_engine_t engine = { + "RMT", + init_engine, + init_step_pin, + set_dir_pin, + finish_dir, + start_step, + set_step_pin, + finish_step, + start_unstep, + finish_unstep, + max_pulses_per_sec +}; + +REGISTER_STEP_ENGINE(RMT, &engine); diff --git a/FluidNC/esp32/timed_engine.c b/FluidNC/esp32/timed_engine.c new file mode 100644 index 000000000..5bc74fce2 --- /dev/null +++ b/FluidNC/esp32/timed_engine.c @@ -0,0 +1,73 @@ +// Copyright (c) 2024 - Mitch Bradley +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +// Stepping engine that uses direct GPIO accesses timed by spin loops. + +#include "Driver/step_engine.h" +#include "Driver/fluidnc_gpio.h" +#include "Driver/delay_usecs.h" +#include +#include // IRAM_ATTR + +static uint32_t _pulse_delay_us; +static uint32_t _dir_delay_us; + +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us) { + _dir_delay_us = dir_delay_us; + _pulse_delay_us = pulse_delay_us; + return _pulse_delay_us; +} + +static int init_step_pin(int step_pin, int step_invert) { + return step_pin; +} + +static int _stepPulseEndTime; + +static void IRAM_ATTR set_pin(int pin, int level) { + gpio_write(pin, level); +} + +static void IRAM_ATTR finish_dir() { + delay_us(_dir_delay_us); +} + +static IRAM_ATTR void start_step() {} + +// Instead of waiting here for the step end time, we mark when the +// step pulse should end, then return. The stepper code can then do +// some work that is overlapped with the pulse time. The spin loop +// will happen in start_unstep() +static void IRAM_ATTR finish_step() { + _stepPulseEndTime = usToEndTicks(_pulse_delay_us); +} + +static IRAM_ATTR int start_unstep() { + spinUntil(_stepPulseEndTime); + return 0; +} + +// This is a noop because each gpio_write() takes effect immediately, +// so there is no need to commit multiple GPIO changes. +static IRAM_ATTR void finish_unstep() {} + +static uint32_t max_pulses_per_sec() { + return 1000000 / (2 * _pulse_delay_us); +} + +// clang-format off +static step_engine_t engine = { + "Timed", + init_engine, + init_step_pin, + set_pin, + finish_dir, + start_step, + set_pin, + finish_step, + start_unstep, + finish_unstep, + max_pulses_per_sec +}; + +REGISTER_STEP_ENGINE(Timed, &engine); diff --git a/FluidNC/include/Driver/delay_usecs.h b/FluidNC/include/Driver/delay_usecs.h index 4e2c8d213..22b59fb8f 100644 --- a/FluidNC/include/Driver/delay_usecs.h +++ b/FluidNC/include/Driver/delay_usecs.h @@ -1,4 +1,8 @@ -#include +#include + +#ifdef __cplusplus +extern "C" { +#endif extern uint32_t ticks_per_us; @@ -11,3 +15,7 @@ void delay_us(int32_t us); int32_t usToCpuTicks(int32_t us); int32_t usToEndTicks(int32_t us); int32_t getCpuTicks(); + +#ifdef __cplusplus +} +#endif diff --git a/FluidNC/include/Driver/fluidnc_gpio.h b/FluidNC/include/Driver/fluidnc_gpio.h index 64542d6ea..2f938c77a 100644 --- a/FluidNC/include/Driver/fluidnc_gpio.h +++ b/FluidNC/include/Driver/fluidnc_gpio.h @@ -3,23 +3,32 @@ #pragma once -#include "src/Pins/PinDetail.h" // pinnum_t +#ifdef __cplusplus +extern "C" { +#endif + +#if 0 +# include "src/Pins/PinDetail.h" // pinnum_t +#else +typedef uint8_t pinnum_t; +#endif // GPIO interface -void gpio_write(pinnum_t pin, bool value); -bool gpio_read(pinnum_t pin); -void gpio_mode(pinnum_t pin, bool input, bool output, bool pullup, bool pulldown, bool opendrain = false); +void gpio_write(pinnum_t pin, int value); +int gpio_read(pinnum_t pin); +void gpio_mode(pinnum_t pin, int input, int output, int pullup, int pulldown, int opendrain); void gpio_set_interrupt_type(pinnum_t pin, int mode); void gpio_add_interrupt(pinnum_t pin, int mode, void (*callback)(void*), void* arg); void gpio_remove_interrupt(pinnum_t pin); void gpio_route(pinnum_t pin, uint32_t signal); -class Print; -void gpio_dump(Print& out); - -typedef void (*gpio_dispatch_t)(int, void*, bool); +typedef void (*gpio_dispatch_t)(int, void*, int); -void gpio_set_action(int gpio_num, gpio_dispatch_t action, void* arg, bool invert); +void gpio_set_action(int gpio_num, gpio_dispatch_t action, void* arg, int invert); void gpio_clear_action(int gpio_num); void poll_gpios(); + +#ifdef __cplusplus +} +#endif diff --git a/FluidNC/include/Driver/gpio_dump.h b/FluidNC/include/Driver/gpio_dump.h new file mode 100644 index 000000000..a4409e2d1 --- /dev/null +++ b/FluidNC/include/Driver/gpio_dump.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void gpio_dump(Print& out); diff --git a/FluidNC/src/I2SOut.h b/FluidNC/include/Driver/i2s_out.h similarity index 76% rename from FluidNC/src/I2SOut.h rename to FluidNC/include/Driver/i2s_out.h index 2a29042b1..d757b0121 100644 --- a/FluidNC/src/I2SOut.h +++ b/FluidNC/include/Driver/i2s_out.h @@ -3,12 +3,12 @@ #pragma once -// It should be included at the outset to know the machine configuration. -#include "Config.h" +#ifdef __cplusplus +extern "C" { +#endif #include -#include "Pin.h" #include "Driver/fluidnc_gpio.h" /* Assert */ @@ -22,10 +22,6 @@ // # define I2SO(n) (I2S_OUT_PIN_BASE + n) -/* 16-bit mode: 1000000 usec / ((160000000 Hz) / 10 / 2) x 16 bit/pulse x 2(stereo) = 4 usec/pulse */ -/* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ -const uint32_t I2S_OUT_USEC_PER_PULSE = 4; - // The longest pulse that we allow when using I2S. It is affected by the // FIFO depth and could probably be a bit longer, but empirically this is // enough for all known stepper drivers. @@ -56,20 +52,7 @@ typedef struct { Initialize I2S out by parameters. return -1 ... already initialized */ -int i2s_out_init(i2s_out_init_t& init_param); - -/* - Initialize I2S out by default parameters. - i2s_out_init_t default_param = { - .ws_pin = I2S_OUT_WS, - .bck_pin = I2S_OUT_BCK, - .data_pin = I2S_OUT_DATA, - .pulse_period = I2S_OUT_USEC_PER_PULSE, - .init_val = I2S_OUT_INIT_VAL, - }; - return -1 ... already initialized -*/ -int i2s_out_init(); +int i2s_out_init(i2s_out_init_t* init_param); /* Read a bit state from the internal pin state var. @@ -101,3 +84,7 @@ void i2s_out_delay(); Reference: "ESP32 Technical Reference Manual" by Espressif Systems https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf */ + +#ifdef __cplusplus +} +#endif diff --git a/FluidNC/src/I2SOut.cpp b/FluidNC/src/I2SOut.cpp deleted file mode 100644 index 10fdd5abb..000000000 --- a/FluidNC/src/I2SOut.cpp +++ /dev/null @@ -1,454 +0,0 @@ -// Copyright (c) 2018 - Simon Jouet -// Copyright (c) 2020 - Michiyasu Odaki -// Copyright (c) 2020 - Mitch Bradley -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#include "I2SOut.h" - -#include - -#ifndef CONFIG_IDF_TARGET_ESP32 -// The newer ESP32 variants have quite different I2S hardware engines -// then the old ESP32 hardware. For now we stub out I2S support for new ESP32s - -uint8_t i2s_out_read(pinnum_t pin) { - return 0; -} -void i2s_out_write(pinnum_t pin, uint8_t val) {} -void i2s_out_delay() {} - -void IRAM_ATTR i2s_out_push_fifo(int count) {} - -int i2s_out_init() { - return -1; -} -#else -# include "Config.h" -# include "Pin.h" -# include "Settings.h" -# include "SettingsDefinitions.h" -# include "Machine/MachineConfig.h" -# include "Stepper.h" - -# include // IRAM_ATTR - -# include -# include -# include -# include -# include -# include -# include "Driver/fluidnc_gpio.h" - -// The library routines are not in IRAM so they can crash when called from FLASH -// The GCC intrinsic versions which are prefixed with __ are compiled inline -# define USE_INLINE_ATOMIC - -# ifdef USE_INLINE_ATOMIC -# define MEMORY_MODEL_FETCH __ATOMIC_RELAXED -# define MEMORY_MODEL_STORE __ATOMIC_RELAXED -# define ATOMIC_LOAD(var) __atomic_load_n(var, MEMORY_MODEL_FETCH) -# define ATOMIC_STORE(var, val) __atomic_store_n(var, val, MEMORY_MODEL_STORE) -# define ATOMIC_FETCH_AND(var, val) __atomic_fetch_and(var, val, MEMORY_MODEL_FETCH) -# define ATOMIC_FETCH_OR(var, val) __atomic_fetch_or(var, val, MEMORY_MODEL_FETCH) -static uint32_t i2s_out_port_data = 0; -# else -# include -# define ATOMIC_LOAD(var) atomic_load(var) -# define ATOMIC_STORE(var, val) atomic_store(var, val) -# define ATOMIC_FETCH_AND(var, val) atomic_fetch_and(var, val) -# define ATOMIC_FETCH_OR(var, val) atomic_fetch_or(var, val) -static std::atomic i2s_out_port_data = ATOMIC_VAR_INIT(0); - -# endif - -const int I2S_SAMPLE_SIZE = 4; /* 4 bytes, 32 bits per sample */ - -// inner lock -static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; -# define I2S_OUT_ENTER_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portENTER_CRITICAL_ISR(&i2s_out_spinlock); \ - } else { \ - portENTER_CRITICAL(&i2s_out_spinlock); \ - } \ - } while (0) -# define I2S_OUT_EXIT_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portEXIT_CRITICAL_ISR(&i2s_out_spinlock); \ - } else { \ - portEXIT_CRITICAL(&i2s_out_spinlock); \ - } \ - } while (0) -# define I2S_OUT_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_spinlock) -# define I2S_OUT_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_spinlock) - -static int i2s_out_initialized = 0; - -static pinnum_t i2s_out_ws_pin = 255; -static pinnum_t i2s_out_bck_pin = 255; -static pinnum_t i2s_out_data_pin = 255; - -// outer lock -static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; -# define I2S_OUT_PULSER_ENTER_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ - } else { \ - portENTER_CRITICAL(&i2s_out_pulser_spinlock); \ - } \ - } while (0) -# define I2S_OUT_PULSER_EXIT_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ - } else { \ - portEXIT_CRITICAL(&i2s_out_pulser_spinlock); \ - } \ - } while (0) -# define I2S_OUT_PULSER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock) -# define I2S_OUT_PULSER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock) - -# if I2S_OUT_NUM_BITS == 16 -# define DATA_SHIFT 16 -# else -# define DATA_SHIFT 0 -# endif - -// -// Internal functions -// -void IRAM_ATTR i2s_out_push_fifo(int count) { - uint32_t portData = ATOMIC_LOAD(&i2s_out_port_data) << DATA_SHIFT; - for (int i = 0; i < count; i++) { - I2S0.fifo_wr = portData; - } -} - -static inline void i2s_out_reset_fifo_without_lock() { - I2S0.conf.rx_fifo_reset = 1; - I2S0.conf.rx_fifo_reset = 0; - I2S0.conf.tx_fifo_reset = 1; - I2S0.conf.tx_fifo_reset = 0; -} - -static int i2s_out_gpio_attach(pinnum_t ws, pinnum_t bck, pinnum_t data) { - // Route the i2s pins to the appropriate GPIO - gpio_route(data, I2S0O_DATA_OUT23_IDX); - gpio_route(bck, I2S0O_BCK_OUT_IDX); - gpio_route(ws, I2S0O_WS_OUT_IDX); - return 0; -} - -const int I2S_OUT_DETACH_PORT_IDX = 0x100; - -static int i2s_out_gpio_detach(pinnum_t ws, pinnum_t bck, pinnum_t data) { - // Route the i2s pins to the appropriate GPIO - gpio_route(ws, I2S_OUT_DETACH_PORT_IDX); - gpio_route(bck, I2S_OUT_DETACH_PORT_IDX); - gpio_route(data, I2S_OUT_DETACH_PORT_IDX); - return 0; -} - -static int i2s_out_gpio_shiftout(uint32_t port_data) { - gpio_write(i2s_out_ws_pin, 0); - for (int i = 0; i < I2S_OUT_NUM_BITS; i++) { - gpio_write(i2s_out_data_pin, !!(port_data & bitnum_to_mask(I2S_OUT_NUM_BITS - 1 - i))); - gpio_write(i2s_out_bck_pin, 1); - gpio_write(i2s_out_bck_pin, 0); - } - gpio_write(i2s_out_ws_pin, 1); // Latch - return 0; -} - -static int i2s_out_stop() { - I2S_OUT_ENTER_CRITICAL(); - - // stop TX module - I2S0.conf.tx_start = 0; - - // Force WS to LOW before detach - // This operation prevents unintended WS edge trigger when detach - gpio_write(i2s_out_ws_pin, 0); - - // Now, detach GPIO pin from I2S - i2s_out_gpio_detach(i2s_out_ws_pin, i2s_out_bck_pin, i2s_out_data_pin); - - // Force BCK to LOW - // After the TX module is stopped, BCK always seems to be in LOW. - // However, I'm going to do it manually to ensure the BCK's LOW. - gpio_write(i2s_out_bck_pin, 0); - - // Transmit recovery data to 74HC595 - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value - i2s_out_gpio_shiftout(port_data); - - //clear pending interrupt - I2S0.int_clr.val = I2S0.int_st.val; - - I2S_OUT_EXIT_CRITICAL(); - return 0; -} - -static int i2s_out_start() { - if (!i2s_out_initialized) { - return -1; - } - - I2S_OUT_ENTER_CRITICAL(); - // Transmit recovery data to 74HC595 - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value - i2s_out_gpio_shiftout(port_data); - - // Attach I2S to specified GPIO pin - i2s_out_gpio_attach(i2s_out_ws_pin, i2s_out_bck_pin, i2s_out_data_pin); - - // reset TX/RX module - I2S0.conf.tx_reset = 1; - I2S0.conf.tx_reset = 0; - I2S0.conf.rx_reset = 1; - I2S0.conf.rx_reset = 0; - - // reset FIFO - i2s_out_reset_fifo_without_lock(); - - I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - I2S0.conf1.tx_stop_en = 1; // BCK and WCK are suppressed while FIFO is empty - - I2S0.int_clr.val = 0xFFFFFFFF; - - I2S0.conf.tx_start = 1; - // Wait for the first FIFO data to prevent the unintentional generation of 0 data - delay_us(20); - I2S0.conf1.tx_stop_en = 0; // BCK and WCK are generated regardless of the FIFO status - - I2S_OUT_EXIT_CRITICAL(); - - return 0; -} - -// -// External funtions -// -void i2s_out_delay() { - I2S_OUT_PULSER_ENTER_CRITICAL(); - // Depending on the timing, it may not be reflected immediately, - // so wait twice as long just in case. - delay_us(I2S_OUT_USEC_PER_PULSE * 2); - I2S_OUT_PULSER_EXIT_CRITICAL(); -} - -void IRAM_ATTR i2s_out_write(pinnum_t pin, uint8_t val) { - uint32_t bit = bitnum_to_mask(pin); - if (val) { - ATOMIC_FETCH_OR(&i2s_out_port_data, bit); - } else { - ATOMIC_FETCH_AND(&i2s_out_port_data, ~bit); - } -} - -uint8_t i2s_out_read(pinnum_t pin) { - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); - return (!!(port_data & bitnum_to_mask(pin))); -} - -// -// Initialize function (external function) -// -int i2s_out_init(i2s_out_init_t& init_param) { - if (i2s_out_initialized) { - // already initialized - return -1; - } - - ATOMIC_STORE(&i2s_out_port_data, init_param.init_val); - - // To make sure hardware is enabled before any hardware register operations. - periph_module_reset(PERIPH_I2S0_MODULE); - periph_module_enable(PERIPH_I2S0_MODULE); - - // Route the i2s pins to the appropriate GPIO - i2s_out_gpio_attach(init_param.ws_pin, init_param.bck_pin, init_param.data_pin); - - /** - * Each i2s transfer will take - * fpll = PLL_D2_CLK -- clka_en = 0 - * - * fi2s = fpll / N + b/a -- N + b/a = clkm_div_num - * fi2s = 160MHz / 2 - * fi2s = 80MHz - * - * fbclk = fi2s / M -- M = tx_bck_div_num - * fbclk = 80MHz / 2 - * fbclk = 40MHz - * - * fwclk = fbclk / 32 - * - * for fwclk = 250kHz(16-bit: 4µS pulse time), 125kHz(32-bit: 8μS pulse time) - * N = 10, b/a = 0 - * M = 2 - * for fwclk = 500kHz(16-bit: 2µS pulse time), 250kHz(32-bit: 4μS pulse time) - * N = 5, b/a = 0 - * M = 2 - * for fwclk = 1000kHz(16-bit: 1µS pulse time), 500kHz(32-bit: 2μS pulse time) - * N = 2, b/a = 2/1 (N + b/a = 2.5) - * M = 2 - */ - - // stop i2s - I2S0.out_link.stop = 1; - I2S0.conf.tx_start = 0; - - // - // i2s_param_config - // - - // configure I2S data port interface. - - //reset i2s - I2S0.conf.tx_reset = 1; - I2S0.conf.tx_reset = 0; - I2S0.conf.rx_reset = 1; - I2S0.conf.rx_reset = 0; - - // A lot of the stuff below could probably be replaced by i2s_set_clk(); - - i2s_out_reset_fifo_without_lock(); - - I2S0.conf2.lcd_en = 0; - I2S0.conf2.camera_en = 0; -# ifdef SOC_I2S_SUPPORTS_PDM_TX - // i2s_ll_tx_enable_pdm(dev, false); - // i2s_ll_tx_enable_pdm(dev2, false); - I2S0.pdm_conf.pcm2pdm_conv_en = 0; - I2S0.pdm_conf.pdm2pcm_conv_en = 0; -# endif - - I2S0.fifo_conf.dscr_en = 0; - - I2S0.conf_chan.tx_chan_mod = 4; // 3:right+constant 4:left+constant (when tx_msb_right = 1) - -# if I2S_OUT_NUM_BITS == 16 - I2S0.fifo_conf.tx_fifo_mod = 0; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.fifo_conf.rx_fifo_mod = 0; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.sample_rate_conf.tx_bits_mod = 16; // default is 16-bits - I2S0.sample_rate_conf.rx_bits_mod = 16; // default is 16-bits -# else - I2S0.fifo_conf.tx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data - I2S0.fifo_conf.rx_fifo_mod = 3; // 0: 16-bit dual channel data, 3: 32-bit single channel data - // Data width is 32-bit. Forgetting this setting will result in a 16-bit transfer. - I2S0.sample_rate_conf.tx_bits_mod = 32; - I2S0.sample_rate_conf.rx_bits_mod = 32; -# endif - I2S0.conf.tx_mono = 0; // Set this bit to enable transmitter’s mono mode in PCM standard mode. - - I2S0.conf_chan.rx_chan_mod = 1; // 1: right+right - I2S0.conf.rx_mono = 0; - - I2S0.fifo_conf.dscr_en = 0; // FIFO is not connected to DMA - I2S0.conf.tx_start = 0; - I2S0.conf.rx_start = 0; - - I2S0.conf.tx_msb_right = 1; // Set this bit to place right-channel data at the MSB in the transmit FIFO. - I2S0.conf.tx_right_first = 0; // Setting this bit allows the right-channel data to be sent first. - - I2S0.conf.tx_slave_mod = 0; // Master - I2S0.fifo_conf.tx_fifo_mod_force_en = 1; //The bit should always be set to 1. -# ifdef SOC_I2S_SUPPORTS_PDM_RX - //i2s_ll_rx_enable_pdm(dev, false); - I2S0.pdm_conf.rx_pdm_en = 0; // Set this bit to enable receiver’s PDM mode. -# endif -# ifdef SOC_I2S_SUPPORTS_PDM_TX - //i2s_ll_tx_enable_pdm(dev, false); - I2S0.pdm_conf.tx_pdm_en = 0; // Set this bit to enable transmitter’s PDM mode. -# endif - - // I2S_COMM_FORMAT_I2S_LSB - I2S0.conf.tx_short_sync = 0; // Set this bit to enable transmitter in PCM standard mode. - I2S0.conf.rx_short_sync = 0; // Set this bit to enable receiver in PCM standard mode. - I2S0.conf.tx_msb_shift = 0; // Do not use the Philips standard to avoid bit-shifting - I2S0.conf.rx_msb_shift = 0; // Do not use the Philips standard to avoid bit-shifting - - // - // i2s_set_clk - // - - // set clock (fi2s) 160MHz / 5 -# ifdef CONFIG_IDF_TARGET_ESP32 - // i2s_ll_rx_clk_set_src(dev, I2S_CLK_D2CLK); - I2S0.clkm_conf.clka_en = 0; // Use 160 MHz PLL_D2_CLK as reference -# endif - // N + b/a = 0 -# if I2S_OUT_NUM_BITS == 16 - // N = 10 - I2S0.clkm_conf.clkm_div_num = 10; // minimum value of 2, reset value of 4, max 256 (I²S clock divider’s integral value) -# else - // N = 5 - // 5 could be changed to 2 to make I2SO pulse at 312.5 kHZ instead of 125 kHz, but doing so would - // require some changes to deal with pulse lengths that are not an integral number of microseconds. - I2S0.clkm_conf.clkm_div_num = 5; // minimum value of 2, reset value of 4, max 256 (I²S clock divider’s integral value) -# endif - // b/a = 0 - I2S0.clkm_conf.clkm_div_b = 0; // 0 at reset - I2S0.clkm_conf.clkm_div_a = 0; // 0 at reset, what about divide by 0? (not an issue) - - // Bit clock configuration bit in transmitter mode. - // fbck = fi2s / tx_bck_div_num = (160 MHz / 5) / 2 = 16 MHz - I2S0.sample_rate_conf.tx_bck_div_num = 2; // minimum value of 2 defaults to 6 - I2S0.sample_rate_conf.rx_bck_div_num = 2; - - // Remember GPIO pin numbers - i2s_out_ws_pin = init_param.ws_pin; - i2s_out_bck_pin = init_param.bck_pin; - i2s_out_data_pin = init_param.data_pin; - i2s_out_initialized = 1; - - // Start the I2S peripheral - i2s_out_start(); - - return 0; -} - -# ifndef I2S_OUT_INIT_VAL -# define I2S_OUT_INIT_VAL 0 -# endif -/* - Initialize I2S out by default parameters. - - return -1 ... already initialized -*/ -int i2s_out_init() { - auto i2so = config->_i2so; - if (!i2so) { - return -1; - } - - Pin& wsPin = i2so->_ws; - Pin& bckPin = i2so->_bck; - Pin& dataPin = i2so->_data; - - // Check capabilities: - if (!wsPin.capabilities().has(Pin::Capabilities::Output | Pin::Capabilities::Native)) { - log_info("Not setting up I2SO: WS pin has incorrect capabilities"); - return -1; - } else if (!bckPin.capabilities().has(Pin::Capabilities::Output | Pin::Capabilities::Native)) { - log_info("Not setting up I2SO: BCK pin has incorrect capabilities"); - return -1; - } else if (!dataPin.capabilities().has(Pin::Capabilities::Output | Pin::Capabilities::Native)) { - log_info("Not setting up I2SO: DATA pin has incorrect capabilities"); - return -1; - } else { - i2s_out_init_t default_param; - default_param.ws_pin = wsPin.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native); - default_param.bck_pin = bckPin.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native); - default_param.data_pin = dataPin.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native); - default_param.pulse_period = I2S_OUT_USEC_PER_PULSE; - default_param.init_val = I2S_OUT_INIT_VAL; - - return i2s_out_init(default_param); - } -} -#endif diff --git a/FluidNC/src/Machine/Axis.cpp b/FluidNC/src/Machine/Axis.cpp index e66ed5c32..b206cb268 100644 --- a/FluidNC/src/Machine/Axis.cpp +++ b/FluidNC/src/Machine/Axis.cpp @@ -25,15 +25,16 @@ namespace Machine { } void Axis::afterParse() { - uint32_t stepRate = uint32_t(_stepsPerMm * _maxRate / 60.0); - auto maxRate = Stepping::maxPulsesPerSec(); - Assert(stepRate <= maxRate, "Stepping rate %d steps/sec exceeds the maximum rate %d", stepRate, maxRate); if (_motors[0] == nullptr) { _motors[0] = new Machine::Motor(_axis, 0); } } void Axis::init() { + uint32_t stepRate = uint32_t(_stepsPerMm * _maxRate / 60.0); + auto maxRate = Stepping::maxPulsesPerSec(); + Assert(stepRate <= maxRate, "Stepping rate %d steps/sec exceeds the maximum rate %d", stepRate, maxRate); + for (size_t i = 0; i < Axis::MAX_MOTORS_PER_AXIS; i++) { auto m = _motors[i]; if (m) { diff --git a/FluidNC/src/Machine/I2SOBus.cpp b/FluidNC/src/Machine/I2SOBus.cpp index 10394679d..7008e851c 100644 --- a/FluidNC/src/Machine/I2SOBus.cpp +++ b/FluidNC/src/Machine/I2SOBus.cpp @@ -3,7 +3,7 @@ // Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. #include "I2SOBus.h" -#include "../I2SOut.h" +#include "Driver/i2s_out.h" // i2s_out_init() namespace Machine { void I2SOBus::validate() { @@ -22,6 +22,25 @@ namespace Machine { void I2SOBus::init() { log_info("I2SO BCK:" << _bck.name() << " WS:" << _ws.name() << " DATA:" << _data.name()); - i2s_out_init(); + + // Check capabilities: + if (!_ws.capabilities().has(Pin::Capabilities::Output | Pin::Capabilities::Native)) { + log_info("Not setting up I2SO: WS pin has incorrect capabilities"); + return; + } else if (!_bck.capabilities().has(Pin::Capabilities::Output | Pin::Capabilities::Native)) { + log_info("Not setting up I2SO: BCK pin has incorrect capabilities"); + return; + } else if (!_data.capabilities().has(Pin::Capabilities::Output | Pin::Capabilities::Native)) { + log_info("Not setting up I2SO: DATA pin has incorrect capabilities"); + return; + } else { + i2s_out_init_t params; + params.ws_pin = _ws.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native); + params.bck_pin = _bck.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native); + params.data_pin = _data.getNative(Pin::Capabilities::Output | Pin::Capabilities::Native); + params.init_val = 0; + + i2s_out_init(¶ms); + } } } diff --git a/FluidNC/src/Pins/GPIOPinDetail.cpp b/FluidNC/src/Pins/GPIOPinDetail.cpp index 1e40b16b9..1e1f4bcd1 100644 --- a/FluidNC/src/Pins/GPIOPinDetail.cpp +++ b/FluidNC/src/Pins/GPIOPinDetail.cpp @@ -178,7 +178,7 @@ namespace Pins { // This is a callback from the low-level GPIO driver that is invoked after // registerEvent() has been called and the pin becomes active. - void GPIOPinDetail::gpioAction(int gpio_num, void* arg, bool active) { + void GPIOPinDetail::gpioAction(int gpio_num, void* arg, int active) { EventPin* obj = static_cast(arg); obj->trigger(active); } diff --git a/FluidNC/src/Pins/GPIOPinDetail.h b/FluidNC/src/Pins/GPIOPinDetail.h index d6940b395..74dbc1098 100644 --- a/FluidNC/src/Pins/GPIOPinDetail.h +++ b/FluidNC/src/Pins/GPIOPinDetail.h @@ -16,7 +16,7 @@ namespace Pins { bool _lastWrittenValue = false; - static void gpioAction(int, void*, bool); + static void gpioAction(int, void*, int); public: static const int nGPIOPins = 40; diff --git a/FluidNC/src/Pins/I2SOPinDetail.cpp b/FluidNC/src/Pins/I2SOPinDetail.cpp index 0d72fc42e..2ad44a483 100644 --- a/FluidNC/src/Pins/I2SOPinDetail.cpp +++ b/FluidNC/src/Pins/I2SOPinDetail.cpp @@ -4,8 +4,9 @@ #ifdef ESP32 # include "I2SOPinDetail.h" -# include "../I2SOut.h" +# include "Driver/i2s_out.h" // i2s_out_write() etc # include "../Assert.h" +# include // IRAM_ATTR namespace Pins { std::vector I2SOPinDetail::_claimed(nI2SOPins, false); @@ -75,7 +76,7 @@ namespace Pins { // is nothing to do here for them. We basically // just check for conflicts above... - // If the pin is ActiveLow, we should take that into account here: + // Set the initial value of the pin per the configuration i2s_out_write(_index, value.has(PinAttributes::InitialOn) ^ _inverted); } diff --git a/FluidNC/src/ProcessSettings.cpp b/FluidNC/src/ProcessSettings.cpp index ce796cb56..350199f6f 100644 --- a/FluidNC/src/ProcessSettings.cpp +++ b/FluidNC/src/ProcessSettings.cpp @@ -20,7 +20,7 @@ #include "UartChannel.h" // Uart0.write() #include "FileStream.h" // FileStream() #include "StartupLog.h" // startupLog -#include "Driver/fluidnc_gpio.h" // gpio_dump() +#include "Driver/gpio_dump.h" // gpio_dump() #include "FileCommands.h" // make_file_commands() #include "FluidPath.h" diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index 1e7670031..d9d30ccac 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -1,14 +1,22 @@ -#include "I2SOut.h" +// #include "Driver/i2s_out.h" #include "EnumItem.h" #include "Stepping.h" -#include "Stepper.h" #include "Machine/MachineConfig.h" // config -#include -#include - #include +step_engine_t* step_engines = NULL; // Linked list of stepping engines + +step_engine_t* find_engine(const char* name) { + for (step_engine_t* p = step_engines; p; p = p->link) { + // Initial substring match, handles different forms of I2S + if (strncmp(name, p->name, strlen(p->name)) == 0) { + return p; + } + } + return NULL; +} + namespace Machine { // fStepperTimer should be an integer divisor of the bus speed, i.e. of fTimers @@ -18,28 +26,37 @@ namespace Machine { int Stepping::_n_active_axes = 0; - bool Stepping::_switchedStepper = false; - int32_t Stepping::_stepPulseEndTime; - size_t Stepping::_segments = 12; - - int Stepping::_i2sPulseCounts = 2; + bool Stepping::_switchedStepper = false; + size_t Stepping::_segments = 12; uint32_t Stepping::_idleMsecs = 255; uint32_t Stepping::_pulseUsecs = 4; uint32_t Stepping::_directionDelayUsecs = 0; uint32_t Stepping::_disableDelayUsecs = 0; + step_engine_t* Stepping::step_engine; + const EnumItem stepTypes[] = { { Stepping::TIMED, "Timed" }, { Stepping::RMT_ENGINE, "RMT" }, { Stepping::I2S_STATIC, "I2S_static" }, { Stepping::I2S_STREAM, "I2S_stream" }, EnumItem(Stepping::RMT_ENGINE) }; + void Stepping::afterParse() { + const char* name = stepTypes[_engine].name; + step_engine = find_engine(name); + Assert(step_engine, "Cannot find stepping engine for %s", name); + Assert(strcmp("I2S", name) || config->_i2so, "I2SO bus must be configured for this stepping type"); + } + void Stepping::init() { log_info("Stepping:" << stepTypes[_engine].name << " Pulse:" << _pulseUsecs << "us Dsbl Delay:" << _disableDelayUsecs - << "us Dir Delay:" << _directionDelayUsecs << "us Idle Delay:" << _idleMsecs << "ms" - << " Pulses: " << _i2sPulseCounts); + << "us Dir Delay:" << _directionDelayUsecs << "us Idle Delay:" << _idleMsecs << "ms"); + uint32_t actual = step_engine->init(_directionDelayUsecs, _pulseUsecs); + if (actual != _pulseUsecs) { + log_warn("stepping/pulse_us adjusted to " << actual); + } // Prepare stepping interrupt callbacks. The one that is actually // used is determined by timerStart() and timerStop() @@ -53,280 +70,151 @@ namespace Machine { Stepper::init(); } - static int init_rmt_channel(int step_gpio, bool invert_step, uint32_t dir_delay_ms, uint32_t pulse_us) { - static rmt_channel_t next_RMT_chan_num = RMT_CHANNEL_0; - if (next_RMT_chan_num == RMT_CHANNEL_MAX) { - log_error("Out of RMT channels"); - return -1; - } - rmt_channel_t rmt_chan_num = next_RMT_chan_num; - next_RMT_chan_num = static_cast(static_cast(next_RMT_chan_num) + 1); - - rmt_config_t rmtConfig = { .rmt_mode = RMT_MODE_TX, - .channel = rmt_chan_num, - .gpio_num = gpio_num_t(step_gpio), - .clk_div = 20, - .mem_block_num = 2, - .flags = 0, - .tx_config = { - .carrier_freq_hz = 0, - .carrier_level = RMT_CARRIER_LEVEL_LOW, - .idle_level = invert_step ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW, - .carrier_duty_percent = 50, -#if SOC_RMT_SUPPORT_TX_LOOP_COUNT - .loop_count = 1, -#endif - .carrier_en = false, - .loop_en = false, - .idle_output_en = true, - } }; - - rmt_item32_t rmtItem[2]; - rmtItem[0].duration0 = dir_delay_ms ? dir_delay_ms * 4 : 1; - rmtItem[0].duration1 = 4 * pulse_us; - rmtItem[1].duration0 = 0; - rmtItem[1].duration1 = 0; - - rmtItem[0].level0 = rmtConfig.tx_config.idle_level; - rmtItem[0].level1 = !rmtConfig.tx_config.idle_level; - rmt_config(&rmtConfig); - rmt_fill_tx_items(rmtConfig.channel, &rmtItem[0], rmtConfig.mem_block_num, 0); - return static_cast(rmt_chan_num); - } - - Stepping::motor_t* Stepping::axis_motors[MAX_N_AXIS][MAX_MOTORS_PER_AXIS] = { nullptr }; +} - void Stepping::assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert) { - if (axis >= _n_active_axes) { - _n_active_axes = axis + 1; - } - if (_engine == RMT_ENGINE) { - step_pin = init_rmt_channel(step_pin, step_invert, _directionDelayUsecs, _pulseUsecs); - } +Stepping::motor_t* Stepping::axis_motors[MAX_N_AXIS][MAX_MOTORS_PER_AXIS] = { nullptr }; - motor_t* m = new motor_t; - axis_motors[axis][motor] = m; - m->step_pin = step_pin; - m->step_invert = step_invert; - m->dir_pin = dir_pin; - m->dir_invert = dir_invert; - m->blocked = false; - m->limited = false; +void Stepping::assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert) { + if (axis >= _n_active_axes) { + _n_active_axes = axis + 1; } + step_pin = step_engine->init_step_pin(step_pin, step_invert); - int Stepping::axis_steps[MAX_N_AXIS] = { 0 }; + motor_t* m = new motor_t; + axis_motors[axis][motor] = m; + m->step_pin = step_pin; + m->step_invert = step_invert; + m->dir_pin = dir_pin; + m->dir_invert = dir_invert; + m->blocked = false; + m->limited = false; +} - bool* Stepping::limit_var(int axis, int motor) { - auto m = axis_motors[axis][motor]; - return m ? &(m->limited) : nullptr; - } +int Stepping::axis_steps[MAX_N_AXIS] = { 0 }; - void Stepping::block(int axis, int motor) { - auto m = axis_motors[axis][motor]; - if (m) { - m->blocked = true; - } +bool* Stepping::limit_var(int axis, int motor) { + auto m = axis_motors[axis][motor]; + return m ? &(m->limited) : nullptr; +} + +void Stepping::block(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->blocked = true; } +} - void Stepping::unblock(int axis, int motor) { - auto m = axis_motors[axis][motor]; - if (m) { - m->blocked = false; - } +void Stepping::unblock(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->blocked = false; } +} - void Stepping::limit(int axis, int motor) { - auto m = axis_motors[axis][motor]; - if (m) { - m->limited = true; - } +void Stepping::limit(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->limited = true; } - void Stepping::unlimit(int axis, int motor) { - auto m = axis_motors[axis][motor]; - if (m) { - m->limited = false; - } +} +void Stepping::unlimit(int axis, int motor) { + auto m = axis_motors[axis][motor]; + if (m) { + m->limited = false; } +} - void IRAM_ATTR Stepping::step(uint8_t step_mask, uint8_t dir_mask) { - // Set the direction pins, but optimize for the common - // situation where the direction bits haven't changed. - static uint8_t previous_dir_mask = 255; // should never be this value - if (previous_dir_mask == 255) { - // Set all the direction bits the first time - previous_dir_mask = ~dir_mask; - } - - if (dir_mask != previous_dir_mask) { - for (size_t axis = 0; axis < _n_active_axes; axis++) { - bool dir = bitnum_is_true(dir_mask, axis); - bool old_dir = bitnum_is_true(previous_dir_mask, axis); - if (dir != old_dir) { - for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { - auto m = axis_motors[axis][motor]; - if (m) { - int pin = m->dir_pin; - bool direction = dir ^ m->dir_invert; - if (_engine == RMT_ENGINE || _engine == TIMED) { - gpio_write(pin, direction); - } else if (_engine == I2S_STREAM || _engine == I2S_STATIC) { - i2s_out_write(pin, direction); - } - } - } - } - waitDirection(); - } - previous_dir_mask = dir_mask; - } +void IRAM_ATTR Stepping::step(uint8_t step_mask, uint8_t dir_mask) { + // Set the direction pins, but optimize for the common + // situation where the direction bits haven't changed. + static uint8_t previous_dir_mask = 255; // should never be this value + if (previous_dir_mask == 255) { + // Set all the direction bits the first time + previous_dir_mask = ~dir_mask; + } - // Turn on step pulses for motors that are supposed to step now + if (dir_mask != previous_dir_mask) { for (size_t axis = 0; axis < _n_active_axes; axis++) { - if (bitnum_is_true(step_mask, axis)) { - auto increment = bitnum_is_true(dir_mask, axis) ? -1 : 1; - axis_steps[axis] += increment; + bool dir = bitnum_is_true(dir_mask, axis); + bool old_dir = bitnum_is_true(previous_dir_mask, axis); + if (dir != old_dir) { for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { auto m = axis_motors[axis][motor]; - if (m && !m->blocked && !m->limited) { - int pin = m->step_pin; - bool inverted = m->step_invert; - if (_engine == RMT_ENGINE) { - // Restart the RMT which has already been configured - // for the desired pulse length and polarity -#ifdef CONFIG_IDF_TARGET_ESP32 - RMT.conf_ch[pin].conf1.mem_rd_rst = 1; - RMT.conf_ch[pin].conf1.mem_rd_rst = 0; - RMT.conf_ch[pin].conf1.tx_start = 1; -#endif -#ifdef CONFIG_IDF_TARGET_ESP32S3 - RMT.chnconf0[pin].mem_rd_rst_n = 1; - RMT.chnconf0[pin].mem_rd_rst_n = 0; - RMT.chnconf0[pin].tx_start_n = 1; -#endif - } else if (_engine == I2S_STREAM || _engine == I2S_STATIC) { - i2s_out_write(pin, !inverted); - } else if (_engine == TIMED) { - gpio_write(pin, !inverted); - } + if (m) { + step_engine->set_dir_pin(m->dir_pin, dir ^ m->dir_invert); } } } + // Some stepper drivers need time between changing direction and doing a pulse. + step_engine->finish_dir(); } - // Do not use switch() in IRAM - if (_engine == I2S_STREAM || _engine == I2S_STATIC) { - i2s_out_push_fifo(_i2sPulseCounts); - } else if (_engine == stepper_id_t::TIMED) { - _stepPulseEndTime = usToEndTicks(_pulseUsecs); - } + previous_dir_mask = dir_mask; } - // Turn all stepper pins off - void IRAM_ATTR Stepping::unstep() { - // With RMT, the end of the step is automatic - if (_engine == RMT_ENGINE) { - return; - } - if (_engine == TIMED) { // Wait pulse - spinUntil(_stepPulseEndTime); - } - for (size_t axis = 0; axis < _n_active_axes; axis++) { + // Turn on step pulses for motors that are supposed to step now + for (size_t axis = 0; axis < _n_active_axes; axis++) { + if (bitnum_is_true(step_mask, axis)) { + auto increment = bitnum_is_true(dir_mask, axis) ? -1 : 1; + axis_steps[axis] += increment; for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { auto m = axis_motors[axis][motor]; - if (m) { - int pin = m->step_pin; - bool inverted = m->step_invert; - if (_engine == I2S_STREAM || _engine == I2S_STATIC) { - i2s_out_write(pin, inverted); - } else if (_engine == TIMED) { - gpio_write(pin, inverted); - } + if (m && !m->blocked && !m->limited) { + step_engine->set_step_pin(m->step_pin, !m->step_invert); } } } - if (_engine == I2S_STREAM || _engine == I2S_STATIC) { - i2s_out_push_fifo(_i2sPulseCounts); - } } + step_engine->finish_step(); +} - void Stepping::reset() {} - void Stepping::beginLowLatency() {} - void Stepping::endLowLatency() {} - - // Called only from step() - void IRAM_ATTR Stepping::waitDirection() { - if (_directionDelayUsecs) { - // Stepper drivers need some time between changing direction and doing a pulse. - // Do not use switch() in IRAM - if (_engine == stepper_id_t::I2S_STREAM || _engine == stepper_id_t::I2S_STATIC) { - // Commit the pin changes to the hardware immediately - // i2s_out_push(); - i2s_out_push_fifo(1); - delay_us(_directionDelayUsecs); - } else if (_engine == stepper_id_t::TIMED) { - // If we are using RMT, we can't delay here. - delay_us(_directionDelayUsecs); +// Turn all stepper pins off +void IRAM_ATTR Stepping::unstep() { + if (step_engine->start_unstep()) { + return; + } + for (size_t axis = 0; axis < _n_active_axes; axis++) { + for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { + auto m = axis_motors[axis][motor]; + if (m) { + step_engine->set_step_pin(m->step_pin, m->step_invert); } } } + step_engine->finish_unstep(); +} - // Called only from Stepper::pulse_func when a new segment is loaded - // The argument is in units of ticks of the timer that generates ISRs - void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { - stepTimerSetTicks((uint32_t)timerTicks); - } +void Stepping::reset() {} +void Stepping::beginLowLatency() {} +void Stepping::endLowLatency() {} - // Called only from Stepper::wake_up which is not used in ISR context - void Stepping::startTimer() { - stepTimerStart(); - } - // Called only from Stepper::stop_stepping, used in both ISR and foreground contexts - void IRAM_ATTR Stepping::stopTimer() { - stepTimerStop(); - } +// Called only from step() +void IRAM_ATTR Stepping::waitDirection() {} - void Stepping::group(Configuration::HandlerBase& handler) { - handler.item("engine", _engine, stepTypes); - handler.item("idle_ms", _idleMsecs, 0, 10000000); // full range - handler.item("pulse_us", _pulseUsecs, 0, 30); - handler.item("dir_delay_us", _directionDelayUsecs, 0, 10); - handler.item("disable_delay_us", _disableDelayUsecs, 0, 1000000); // max 1 second - handler.item("segments", _segments, 6, 20); - } +// Called only from Stepper::pulse_func when a new segment is loaded +// The argument is in units of ticks of the timer that generates ISRs +void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { + // stepTimerSetTicks((uint32_t)timerTicks * 3 / 10); + stepTimerSetTicks((uint32_t)timerTicks / 2); +} - void Stepping::afterParse() { - if (_engine == I2S_STREAM || _engine == I2S_STATIC) { - Assert(config->_i2so, "I2SO bus must be configured for this stepping type"); - if (_pulseUsecs < I2S_OUT_USEC_PER_PULSE) { - log_warn("Increasing stepping/pulse_us to the IS2 minimum value " << I2S_OUT_USEC_PER_PULSE); - _pulseUsecs = I2S_OUT_USEC_PER_PULSE; - } - if ((_engine == I2S_STREAM || _engine == I2S_STATIC) && _pulseUsecs > I2S_MAX_USEC_PER_PULSE) { - log_warn("Decreasing stepping/pulse_us to " << I2S_MAX_USEC_PER_PULSE << ", the maximum value for I2S"); - _pulseUsecs = I2S_MAX_USEC_PER_PULSE; - } - } - if (_engine == I2S_STREAM || _engine == I2S_STATIC) { - // Number of I2S frames for a pulse, rounded up - _i2sPulseCounts = (_pulseUsecs + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; - } - } +// Called only from Stepper::wake_up which is not used in ISR context +void Stepping::startTimer() { + stepTimerStart(); +} +// Called only from Stepper::stop_stepping, used in both ISR and foreground contexts +void IRAM_ATTR Stepping::stopTimer() { + stepTimerStop(); +} - uint32_t Stepping::maxPulsesPerSec() { - uint32_t pps; - switch (_engine) { - case I2S_STREAM: - case I2S_STATIC: - pps = 1000000 / ((2 * _i2sPulseCounts) * I2S_OUT_USEC_PER_PULSE); - break; - case RMT_ENGINE: - pps = 1000000 / (2 * _pulseUsecs + _directionDelayUsecs); - // fall through - case TIMED: - default: - if (pps > 80000) { // Based on testing - pps = 80000; - } - } - return pps; - } +void Stepping::group(Configuration::HandlerBase& handler) { + handler.item("engine", _engine, stepTypes); + handler.item("idle_ms", _idleMsecs, 0, 10000000); // full range + handler.item("pulse_us", _pulseUsecs, 0, 30); + handler.item("dir_delay_us", _directionDelayUsecs, 0, 10); + handler.item("disable_delay_us", _disableDelayUsecs, 0, 1000000); // max 1 second + handler.item("segments", _segments, 6, 20); +} + +uint32_t Stepping::maxPulsesPerSec() { + return step_engine->max_pulses_per_sec(); } diff --git a/FluidNC/src/Stepping.h b/FluidNC/src/Stepping.h index a60ed81c2..fa67a8e2a 100644 --- a/FluidNC/src/Stepping.h +++ b/FluidNC/src/Stepping.h @@ -6,6 +6,8 @@ #include "Configuration/Configurable.h" #include "Driver/StepTimer.h" +#include "Driver/step_engine.h" + namespace Machine { class Stepping : public Configuration::Configurable { public: @@ -32,6 +34,8 @@ namespace Machine { static void waitDirection(); // Wait for direction delay static int32_t axis_steps[MAX_N_AXIS]; + static step_engine_t* step_engine; + public: enum stepper_id_t { TIMED = 0, diff --git a/platformio.ini b/platformio.ini index bfda59b3f..2607aff01 100644 --- a/platformio.ini +++ b/platformio.ini @@ -121,6 +121,7 @@ build_src_filter = ${common_esp32_base.build_src_filter} ${common_bt.build_src_f extends = common_esp32_s3 lib_deps = ${common.lib_deps} build_src_filter = ${common_esp32_base.build_src_filter} +build_flags = ${common_esp32_base.build_flags} [env:wifi_s3] extends = common_esp32_s3 From 2403d439f8a3047536cdb1fa9b71b2d1ad9b9fc1 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Wed, 9 Oct 2024 10:26:31 -1000 Subject: [PATCH 12/30] Block HTTP synchronous commands while moving --- FluidNC/src/WebUI/WebServer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FluidNC/src/WebUI/WebServer.cpp b/FluidNC/src/WebUI/WebServer.cpp index 3daef87c9..5919ce646 100644 --- a/FluidNC/src/WebUI/WebServer.cpp +++ b/FluidNC/src/WebUI/WebServer.cpp @@ -437,6 +437,10 @@ namespace WebUI { return -1; } void Web_Server::synchronousCommand(const char* cmd, bool silent, AuthenticationLevel auth_level) { + if (http_block_during_motion->get() && inMotionState()) { + _webserver->send(503, "text/plain", "Try again when not moving\n"); + return; + } char line[256]; strncpy(line, cmd, 255); webClient.attachWS(_webserver, silent); From 875e627d9d9e049c0dbc6a9619597f42eee90925 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Wed, 9 Oct 2024 11:33:46 -1000 Subject: [PATCH 13/30] Dollar commands are asynchronous by default; helps WebUI --- FluidNC/src/ProcessSettings.cpp | 2 -- FluidNC/src/Settings.h | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/FluidNC/src/ProcessSettings.cpp b/FluidNC/src/ProcessSettings.cpp index 350199f6f..f080ac512 100644 --- a/FluidNC/src/ProcessSettings.cpp +++ b/FluidNC/src/ProcessSettings.cpp @@ -899,8 +899,6 @@ Error do_command_or_setting(const char* key, const char* value, AuthenticationLe } } - protocol_buffer_synchronize(); - // First search the yaml settings by name. If found, set a new // value if one is given, otherwise display the current value try { diff --git a/FluidNC/src/Settings.h b/FluidNC/src/Settings.h index 3ce7299aa..25d885191 100644 --- a/FluidNC/src/Settings.h +++ b/FluidNC/src/Settings.h @@ -100,7 +100,7 @@ class Command : public Word { const char* grblName, const char* fullName, bool (*cmdChecker)(), - bool synchronous = true); + bool synchronous = false); // The default implementation of addWebui() does nothing. // Derived classes may override it to do something. From b8e2c587d51a53cdfdb36a6b330eb511b1230628 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Wed, 9 Oct 2024 11:33:46 -1000 Subject: [PATCH 14/30] Checked in missing file --- FluidNC/include/Driver/step_engine.h | 67 ++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 FluidNC/include/Driver/step_engine.h diff --git a/FluidNC/include/Driver/step_engine.h b/FluidNC/include/Driver/step_engine.h new file mode 100644 index 000000000..82431337f --- /dev/null +++ b/FluidNC/include/Driver/step_engine.h @@ -0,0 +1,67 @@ +// Copyright (c) 2024 - Mitch Bradley +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +// Interface between Stepping.cpp and low-level stepping engine drivers +// This is in C instead of C++ to make it easy to force the relevant pieces +// to be in RAM or IRAM, thus avoiding ESP32 problems with accessing FLASH +// from interrupt service routines. + +#pragma once + +#include + +typedef struct step_engine { + const char* name; + + // Prepare the engine for use + // The return value is the actual pulse delay according to the + // characteristics of the engine. + uint32_t (*init)(uint32_t dir_delay_us, uint32_t pulse_delay_us); + + // Setup the step pin, returning a number to identify it. + // In many cases, the return value is the same as pin, but some step + // engines might allocate a surrogate object and return its ID + int (*init_step_pin)(int pin, int inverted); + + // Set the state of the direction pin to level + void (*set_dir_pin)(int pin, int level); + + // Commit all of the direction pin changes and wait for dir_delay_us + // if necessary + void (*finish_dir)(); + + // Set the state of the step pin to level + void (*start_step)(); + + // Set the state of the step pin to level + void (*set_step_pin)(int pin, int level); + + // Commit all of the direction pin changes and either wait for pulse_delay_us + // or arrange for start_unstep to do it + void (*finish_step)(); + + // Wait for pulse_delay_us if necessary + // If the return value is true, Stepping.cpp will skip the rest of the + // the unstep process + int (*start_unstep)(); + + // Commit all changes (deassertions) of step pins + void (*finish_unstep)(); + + // The maximum step rate for this engine as a function of dir_delay_us, + // pulse_delay_us, and other characteristics of this stepping engine + uint32_t (*max_pulses_per_sec)(); + + // Link to next engine in the list of registered stepping engines + struct step_engine* link; +} step_engine_t; + +// Linked list of registered step engines +extern step_engine_t* step_engines; + +// clang-format off +#define REGISTER_STEP_ENGINE(name, engine) \ + __attribute__((constructor)) void __register_##name(void) { \ + (engine)->link = step_engines; \ + step_engines = engine; \ + } From 859c0e0a538d553cbeb7e031a01bcde53b502270 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Wed, 9 Oct 2024 17:01:03 -1000 Subject: [PATCH 15/30] Removed debugging printfs. --- FluidNC/esp32/i2s_engine.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index a50644b60..c228b6190 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -375,13 +375,11 @@ int i2s_out_init(i2s_out_init_t* init_param) { delay_us(20); value = *(uint32_t*)(ptr + 0xac); - printf("Clkreg AENA %d b %d a %d n %d\n", (value >> 21) & 1, (value >> 14) & 0x3f, (value >> 8) & 0x3f, value & 0xff); i2s_ll_mclk_div_t div = { 2, 32, 16 }; // b/a = 0.5 i2s_ll_tx_set_clk(&I2S0, &div); value = *(uint32_t*)(ptr + 0xac); - printf("Clkreg AENA %d b %d a %d n %d\n", (value >> 21) & 1, (value >> 14) & 0x3f, (value >> 8) & 0x3f, value & 0xff); // Bit clock configuration bit in transmitter mode. // fbck = fi2s / tx_bck_div_num = (160 MHz / 5) / 2 = 16 MHz @@ -405,7 +403,6 @@ static uint32_t _dir_delay_us; // Convert the delays from microseconds to a number of I2S frame static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us) { - printf("Pus %d\n", pulse_us); if (pulse_us < I2S_OUT_USEC_PER_PULSE) { pulse_us = I2S_OUT_USEC_PER_PULSE; } @@ -413,10 +410,8 @@ static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us) { pulse_us = I2S_MAX_USEC_PER_PULSE; } _dir_delay_us = dir_delay_us; - printf("Pus2 %d Uspp %d\n", pulse_us, I2S_OUT_USEC_PER_PULSE); _pulse_counts = (pulse_us + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; - printf("PC %d plen %d\n", _pulse_counts, _pulse_counts * I2S_OUT_USEC_PER_PULSE); return _pulse_counts * I2S_OUT_USEC_PER_PULSE; } @@ -477,7 +472,6 @@ static IRAM_ATTR int start_unstep() { } static uint32_t max_pulses_per_sec() { - printf("pulse_counts %d, rate %d\n", _pulse_counts, 1000000 / (2 * _pulse_counts * I2S_OUT_USEC_PER_PULSE)); return 1000000 / (2 * _pulse_counts * I2S_OUT_USEC_PER_PULSE); } From 6c079109b0c0f34d7fa0cea688ed7e5e8da9ff89 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Thu, 10 Oct 2024 07:09:23 -1000 Subject: [PATCH 16/30] Fixed inverted direction on I2S --- FluidNC/esp32/i2s_engine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index c228b6190..fe4df8c25 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -423,7 +423,7 @@ static int init_step_pin(int step_pin, int step_invert) { // pin states. Later, that variable will be transferred to // the I2S FIFO to change all the affected pins at once. static IRAM_ATTR void set_dir_pin(int pin, int level) { - i2s_out_write(pin, level); + i2s_out_write(pin, !level); } uint32_t new_port_data; From 621e9552c381de21ef651c2c4e2828307b24df21 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Thu, 10 Oct 2024 11:56:37 -1000 Subject: [PATCH 17/30] Fixed step count, timing and direction issues --- FluidNC/esp32/StepTimer.cpp | 11 +++++++++-- FluidNC/esp32/i2s_engine.c | 18 ++++++++++-------- FluidNC/src/Machine/Axis.cpp | 2 +- FluidNC/src/Stepping.cpp | 5 +++-- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/FluidNC/esp32/StepTimer.cpp b/FluidNC/esp32/StepTimer.cpp index 05336f6be..338698fbd 100644 --- a/FluidNC/esp32/StepTimer.cpp +++ b/FluidNC/esp32/StepTimer.cpp @@ -29,14 +29,21 @@ static void IRAM_ATTR timer_isr(void* arg) { } } +// Possibly-unnecessary optimization to avoid rewriting the alarm value +static uint32_t old_ticks = 0xffffffff; + void IRAM_ATTR stepTimerStart() { timer_ll_set_alarm_value(&TIMERG0, TIMER_0, 10ULL); // Interrupt very soon to start the stepping + old_ticks = 10ULL; timer_ll_set_alarm_enable(&TIMERG0, TIMER_0, true); timer_ll_set_counter_enable(&TIMERG0, TIMER_0, true); } void IRAM_ATTR stepTimerSetTicks(uint32_t ticks) { - timer_ll_set_alarm_value(&TIMERG0, TIMER_0, (uint64_t)ticks); + if (ticks != old_ticks) { + timer_ll_set_alarm_value(&TIMERG0, TIMER_0, (uint64_t)ticks); + old_ticks = ticks; + } } void IRAM_ATTR stepTimerStop() { @@ -61,7 +68,7 @@ void stepTimerInit(uint32_t frequency, bool (*callback)(void)) { timer_isr_callback = callback; esp_intr_alloc_intrstatus(timer_group_periph_signals.groups[TIMER_GROUP_0].t0_irq_id, - ESP_INTR_FLAG_IRAM, + ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3, timer_ll_get_intr_status_reg(&TIMERG0), 1 << TIMER_0, timer_isr, diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index fe4df8c25..967161aa5 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -455,22 +455,24 @@ static IRAM_ATTR void finish_dir() { // push _pulse_counts copies of the memory variable to the // I2S FIFO, thus creating a pulse of the desired length. static IRAM_ATTR void finish_step() { - for (int i = 0; i < _pulse_counts; i++) { - I2S0.fifo_wr = new_port_data; + if (new_port_data == i2s_out_port_data) { + return; } for (int i = 0; i < _pulse_counts; i++) { - I2S0.fifo_wr = i2s_out_port_data; + I2S0.fifo_wr = new_port_data; } + // There is no need for multiple "step off" samples since the timer will not fire + // until the next time for a pulse. + I2S0.fifo_wr = i2s_out_port_data; } static IRAM_ATTR int start_unstep() { -#if 1 return 1; -#else - return 0; -#endif } +// Not called since start_unstep() returns 1 +static IRAM_ATTR void finish_unstep() {} + static uint32_t max_pulses_per_sec() { return 1000000 / (2 * _pulse_counts * I2S_OUT_USEC_PER_PULSE); } @@ -486,7 +488,7 @@ step_engine_t engine = { set_step_pin, finish_step, start_unstep, - finish_step, // finish_step and finish_unstep are the same + finish_unstep, max_pulses_per_sec }; diff --git a/FluidNC/src/Machine/Axis.cpp b/FluidNC/src/Machine/Axis.cpp index b206cb268..09199771f 100644 --- a/FluidNC/src/Machine/Axis.cpp +++ b/FluidNC/src/Machine/Axis.cpp @@ -7,7 +7,7 @@ namespace Machine { void Axis::group(Configuration::HandlerBase& handler) { handler.item("steps_per_mm", _stepsPerMm, 0.001, 100000.0); - handler.item("max_rate_mm_per_min", _maxRate, 0.001, 100000.0); + handler.item("max_rate_mm_per_min", _maxRate, 0.001, 200000.0); handler.item("acceleration_mm_per_sec2", _acceleration, 0.001, 100000.0); handler.item("max_travel_mm", _maxTravel, 0.1, 10000000.0); handler.item("soft_limits", _softLimits); diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index d9d30ccac..6e00043d5 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -151,6 +151,8 @@ void IRAM_ATTR Stepping::step(uint8_t step_mask, uint8_t dir_mask) { previous_dir_mask = dir_mask; } + step_engine->start_step(); + // Turn on step pulses for motors that are supposed to step now for (size_t axis = 0; axis < _n_active_axes; axis++) { if (bitnum_is_true(step_mask, axis)) { @@ -193,8 +195,7 @@ void IRAM_ATTR Stepping::waitDirection() {} // Called only from Stepper::pulse_func when a new segment is loaded // The argument is in units of ticks of the timer that generates ISRs void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { - // stepTimerSetTicks((uint32_t)timerTicks * 3 / 10); - stepTimerSetTicks((uint32_t)timerTicks / 2); + stepTimerSetTicks((uint32_t)timerTicks); } // Called only from Stepper::wake_up which is not used in ISR context From b160dfec336f3eb9ee08554e793453d60ef44faa Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Thu, 10 Oct 2024 13:54:14 -1000 Subject: [PATCH 18/30] Increase RMT speed limit --- FluidNC/esp32/rmt_engine.c | 8 +++----- FluidNC/src/Machine/Axis.cpp | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/FluidNC/esp32/rmt_engine.c b/FluidNC/esp32/rmt_engine.c index 19d65f09c..e876ab930 100644 --- a/FluidNC/esp32/rmt_engine.c +++ b/FluidNC/esp32/rmt_engine.c @@ -71,6 +71,7 @@ static IRAM_ATTR void set_dir_pin(int pin, int level) { // The direction delay is handled by the RMT pulser static IRAM_ATTR void finish_dir() {} +// No need for any common setup before setting step pins static IRAM_ATTR void start_step() {} // Restart the RMT which has already been configured @@ -99,8 +100,8 @@ static IRAM_ATTR int start_unstep() { return 1; } -// This is a noop and will not be called since start_unstep() -// returned true +// This is a noop and will not be called because start_unstep() +// returns 1 static IRAM_ATTR void finish_unstep() {} // Possible speedup: If the direction delay were done explicitly @@ -109,9 +110,6 @@ static IRAM_ATTR void finish_unstep() {} // and thus do not need to be applied to every pulse static uint32_t max_pulses_per_sec() { uint32_t pps = 1000000 / (2 * _pulse_delay_us + _dir_delay_us); - if (pps > 80000) { // Based on testing - pps = 80000; - } return pps; } diff --git a/FluidNC/src/Machine/Axis.cpp b/FluidNC/src/Machine/Axis.cpp index 09199771f..d39e169f4 100644 --- a/FluidNC/src/Machine/Axis.cpp +++ b/FluidNC/src/Machine/Axis.cpp @@ -7,7 +7,7 @@ namespace Machine { void Axis::group(Configuration::HandlerBase& handler) { handler.item("steps_per_mm", _stepsPerMm, 0.001, 100000.0); - handler.item("max_rate_mm_per_min", _maxRate, 0.001, 200000.0); + handler.item("max_rate_mm_per_min", _maxRate, 0.001, 250000.0); handler.item("acceleration_mm_per_sec2", _acceleration, 0.001, 100000.0); handler.item("max_travel_mm", _maxTravel, 0.1, 10000000.0); handler.item("soft_limits", _softLimits); From cfa1d05bb32f64441d6add1ad6463143f69bc307 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Thu, 10 Oct 2024 13:54:41 -1000 Subject: [PATCH 19/30] Fixed problem with limit switches above GPIO 32 --- FluidNC/esp32/gpio.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FluidNC/esp32/gpio.cpp b/FluidNC/esp32/gpio.cpp index 394960835..3373e061e 100644 --- a/FluidNC/esp32/gpio.cpp +++ b/FluidNC/esp32/gpio.cpp @@ -11,7 +11,7 @@ static gpio_dev_t* _gpio_dev = GPIO_HAL_GET_HW(GPIO_PORT_0); void IRAM_ATTR gpio_write(pinnum_t pin, int value) { - gpio_ll_set_level(_gpio_dev, (gpio_num_t)pin, value); + gpio_ll_set_level(_gpio_dev, (gpio_num_t)pin, (uint32_t)value); } int IRAM_ATTR gpio_read(pinnum_t pin) { return gpio_ll_get_level(_gpio_dev, (gpio_num_t)pin); @@ -95,7 +95,7 @@ static gpio_mask_t gpio_mask(int gpio_num) { static inline int gpio_is_active(int gpio_num) { return get_gpios() & gpio_mask(gpio_num); } -static void gpios_update(gpio_mask_t& gpios, int gpio_num, int active) { +static void gpios_update(gpio_mask_t& gpios, int gpio_num, bool active) { if (active) { gpios |= gpio_mask(gpio_num); } else { @@ -124,7 +124,7 @@ void gpio_clear_action(int gpio_num) { gpios_update(gpios_interest, gpio_num, false); } -static void gpio_send_action(int gpio_num, int active) { +static void gpio_send_action(int gpio_num, bool active) { auto end_ticks = gpio_next_event_ticks[gpio_num]; int32_t this_ticks = int32_t(xTaskGetTickCount()); if (end_ticks == 0 || ((this_ticks - end_ticks) > 0)) { From 4e268bcdf5d446bd4228c507b41de498ed94d183 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Fri, 11 Oct 2024 09:14:43 -1000 Subject: [PATCH 20/30] I2s direction --- FluidNC/esp32/i2s_engine.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index 967161aa5..55e701a9d 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -423,7 +423,7 @@ static int init_step_pin(int step_pin, int step_invert) { // pin states. Later, that variable will be transferred to // the I2S FIFO to change all the affected pins at once. static IRAM_ATTR void set_dir_pin(int pin, int level) { - i2s_out_write(pin, !level); + i2s_out_write(pin, level); } uint32_t new_port_data; From b71fea873f56c89ac250f5ffe2de2a36c5519c7a Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sat, 12 Oct 2024 17:08:09 -1000 Subject: [PATCH 21/30] Fix SPI Trinamic drivers so the register with the stepping engine Standard stepper and Trinamic Uart derivatives worked, but Trinamic SPI driver failed to call their base class's init() method. --- FluidNC/src/Motors/TMC2130Driver.cpp | 2 ++ FluidNC/src/Motors/TMC5160Driver.cpp | 2 ++ FluidNC/src/Motors/TMC5160ProDriver.cpp | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/FluidNC/src/Motors/TMC2130Driver.cpp b/FluidNC/src/Motors/TMC2130Driver.cpp index 62d2855a0..52ab6e19c 100644 --- a/FluidNC/src/Motors/TMC2130Driver.cpp +++ b/FluidNC/src/Motors/TMC2130Driver.cpp @@ -12,6 +12,8 @@ namespace MotorDrivers { void TMC2130Driver::init() { + TrinamicSpiDriver::init(); + uint8_t cs_id; cs_id = setupSPI(); diff --git a/FluidNC/src/Motors/TMC5160Driver.cpp b/FluidNC/src/Motors/TMC5160Driver.cpp index 6d3744e1b..226e67cf2 100644 --- a/FluidNC/src/Motors/TMC5160Driver.cpp +++ b/FluidNC/src/Motors/TMC5160Driver.cpp @@ -8,6 +8,8 @@ namespace MotorDrivers { void TMC5160Driver::init() { + TrinamicSpiDriver::init(); + uint8_t cs_id; cs_id = setupSPI(); diff --git a/FluidNC/src/Motors/TMC5160ProDriver.cpp b/FluidNC/src/Motors/TMC5160ProDriver.cpp index ed1956b25..7a08cbb7f 100644 --- a/FluidNC/src/Motors/TMC5160ProDriver.cpp +++ b/FluidNC/src/Motors/TMC5160ProDriver.cpp @@ -8,6 +8,8 @@ namespace MotorDrivers { void TMC5160ProDriver::init() { + TrinamicSpiDriver::init(); + uint8_t cs_id; cs_id = setupSPI(); @@ -26,7 +28,9 @@ namespace MotorDrivers { TrinamicBase::config_motor(); } - bool TMC5160ProDriver::test() { return checkVersion(0x30, tmc5160->version()); } + bool TMC5160ProDriver::test() { + return checkVersion(0x30, tmc5160->version()); + } void TMC5160ProDriver::set_registers(bool isHoming) { if (_has_errors) { From 303aa8ee158f84c30448eabd708bfe0c178a2064 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 13 Oct 2024 10:07:23 -1000 Subject: [PATCH 22/30] Fixed problem with no-motor axes --- FluidNC/src/Stepping.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index 6e00043d5..d3e2ef12c 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -24,8 +24,6 @@ namespace Machine { int Stepping::_engine = RMT_ENGINE; - int Stepping::_n_active_axes = 0; - bool Stepping::_switchedStepper = false; size_t Stepping::_segments = 12; @@ -75,9 +73,6 @@ namespace Machine { Stepping::motor_t* Stepping::axis_motors[MAX_N_AXIS][MAX_MOTORS_PER_AXIS] = { nullptr }; void Stepping::assignMotor(int axis, int motor, int step_pin, bool step_invert, int dir_pin, bool dir_invert) { - if (axis >= _n_active_axes) { - _n_active_axes = axis + 1; - } step_pin = step_engine->init_step_pin(step_pin, step_invert); motor_t* m = new motor_t; @@ -134,7 +129,7 @@ void IRAM_ATTR Stepping::step(uint8_t step_mask, uint8_t dir_mask) { } if (dir_mask != previous_dir_mask) { - for (size_t axis = 0; axis < _n_active_axes; axis++) { + for (size_t axis = 0; axis < Axes::_numberAxis; axis++) { bool dir = bitnum_is_true(dir_mask, axis); bool old_dir = bitnum_is_true(previous_dir_mask, axis); if (dir != old_dir) { @@ -154,7 +149,7 @@ void IRAM_ATTR Stepping::step(uint8_t step_mask, uint8_t dir_mask) { step_engine->start_step(); // Turn on step pulses for motors that are supposed to step now - for (size_t axis = 0; axis < _n_active_axes; axis++) { + for (size_t axis = 0; axis < Axes::_numberAxis; axis++) { if (bitnum_is_true(step_mask, axis)) { auto increment = bitnum_is_true(dir_mask, axis) ? -1 : 1; axis_steps[axis] += increment; @@ -174,7 +169,7 @@ void IRAM_ATTR Stepping::unstep() { if (step_engine->start_unstep()) { return; } - for (size_t axis = 0; axis < _n_active_axes; axis++) { + for (size_t axis = 0; axis < Axes::_numberAxis; axis++) { for (size_t motor = 0; motor < MAX_MOTORS_PER_AXIS; motor++) { auto m = axis_motors[axis][motor]; if (m) { From f073e00fc97365cbe50952e722b7614ab3462d4a Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 13 Oct 2024 12:59:14 -1000 Subject: [PATCH 23/30] Fix #1355 - I2SO pin inversion ignored --- FluidNC/src/Pins/I2SOPinDetail.h | 1 - 1 file changed, 1 deletion(-) diff --git a/FluidNC/src/Pins/I2SOPinDetail.h b/FluidNC/src/Pins/I2SOPinDetail.h index 1f9e1fadd..c3db604f3 100644 --- a/FluidNC/src/Pins/I2SOPinDetail.h +++ b/FluidNC/src/Pins/I2SOPinDetail.h @@ -10,7 +10,6 @@ namespace Pins { class I2SOPinDetail : public PinDetail { PinCapabilities _capabilities; PinAttributes _attributes; - int _inverted; static const int nI2SOPins = 32; static std::vector _claimed; From ad2cc0af57585c3b4f36932dfa94346dbc5d7e8c Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Thu, 17 Oct 2024 15:06:02 -1000 Subject: [PATCH 24/30] Fixed crash on WiFi disconnect --- FluidNC/ld/esp32/vtable_in_dram.ld | 3 +++ FluidNC/src/Pins/ChannelPinDetail.cpp | 2 +- FluidNC/src/Pins/DebugPinDetail.cpp | 2 +- FluidNC/src/Pins/ErrorPinDetail.cpp | 4 ++-- FluidNC/src/Pins/I2SOPinDetail.cpp | 2 +- FluidNC/src/WebUI/WSChannel.cpp | 22 +++++++++---------- FluidNC/src/WebUI/WifiConfig.cpp | 31 ++++++++++++++++++++++++--- 7 files changed, 46 insertions(+), 20 deletions(-) diff --git a/FluidNC/ld/esp32/vtable_in_dram.ld b/FluidNC/ld/esp32/vtable_in_dram.ld index 0a662d6c2..196984359 100644 --- a/FluidNC/ld/esp32/vtable_in_dram.ld +++ b/FluidNC/ld/esp32/vtable_in_dram.ld @@ -1,6 +1,9 @@ /* List of files and sections to place in RAM instead of FLASH */ /* See README.md in this directory for a complete explanation */ + **Detail.cpp.o(.rodata .rodata.*) +/* *Pin.cpp.o(.rodata .rodata.*) */ + /* All files whose name ends with Spindle.cpp */ /* An earlier version included .xt.prop and .xt.prop.* but */ diff --git a/FluidNC/src/Pins/ChannelPinDetail.cpp b/FluidNC/src/Pins/ChannelPinDetail.cpp index 84972c423..043a2882b 100644 --- a/FluidNC/src/Pins/ChannelPinDetail.cpp +++ b/FluidNC/src/Pins/ChannelPinDetail.cpp @@ -23,7 +23,7 @@ namespace Pins { return PinCapabilities::Output | PinCapabilities::Input | PinCapabilities::PWM | PinCapabilities::Void; } - void ChannelPinDetail::write(int high) { + void IRAM_ATTR ChannelPinDetail::write(int high) { if (high == _value) { return; } diff --git a/FluidNC/src/Pins/DebugPinDetail.cpp b/FluidNC/src/Pins/DebugPinDetail.cpp index 19f205bd2..077beae4f 100644 --- a/FluidNC/src/Pins/DebugPinDetail.cpp +++ b/FluidNC/src/Pins/DebugPinDetail.cpp @@ -9,7 +9,7 @@ namespace Pins { // I/O: - void DebugPinDetail::write(int high) { + void IRAM_ATTR DebugPinDetail::write(int high) { if (high != int(_isHigh)) { _isHigh = bool(high); if (shouldEvent()) { diff --git a/FluidNC/src/Pins/ErrorPinDetail.cpp b/FluidNC/src/Pins/ErrorPinDetail.cpp index 984e789be..29bbc522c 100644 --- a/FluidNC/src/Pins/ErrorPinDetail.cpp +++ b/FluidNC/src/Pins/ErrorPinDetail.cpp @@ -13,7 +13,7 @@ namespace Pins { } #ifdef ESP32 - void ErrorPinDetail::write(int high) { + void IRAM_ATTR ErrorPinDetail::write(int high) { log_error("Cannot write to pin " << _description.c_str() << ". The config is incorrect."); } int ErrorPinDetail::read() { @@ -25,7 +25,7 @@ namespace Pins { } #else - void ErrorPinDetail::write(int high) { + void IRAM_ATTR ErrorPinDetail::write(int high) { Assert(false, "Cannot write to an error pin."); } int ErrorPinDetail::read() { diff --git a/FluidNC/src/Pins/I2SOPinDetail.cpp b/FluidNC/src/Pins/I2SOPinDetail.cpp index 2ad44a483..3ab23bf8f 100644 --- a/FluidNC/src/Pins/I2SOPinDetail.cpp +++ b/FluidNC/src/Pins/I2SOPinDetail.cpp @@ -46,7 +46,7 @@ namespace Pins { // Write and wait for completion. Not suitable for use from an ISR // cppcheck-suppress unusedFunction - void I2SOPinDetail::synchronousWrite(int high) { + void IRAM_ATTR I2SOPinDetail::synchronousWrite(int high) { if (high != _lastWrittenValue) { _lastWrittenValue = high; diff --git a/FluidNC/src/WebUI/WSChannel.cpp b/FluidNC/src/WebUI/WSChannel.cpp index ff81838b0..823695e50 100644 --- a/FluidNC/src/WebUI/WSChannel.cpp +++ b/FluidNC/src/WebUI/WSChannel.cpp @@ -3,6 +3,7 @@ #include "WSChannel.h" +#include "src/UartChannel.h" #include "WebServer.h" #include #include @@ -62,12 +63,10 @@ namespace WebUI { int stat = _server->canSend(_clientNum); if (stat < 0) { _active = false; - log_debug("WebSocket is dead; closing"); return 0; } if (!_server->sendBIN(_clientNum, out, outlen)) { _active = false; - log_debug("WebSocket is unresponsive; closing"); } if (_output_line.length()) { _output_line = ""; @@ -82,7 +81,7 @@ namespace WebUI { } if (!_server->sendTXT(_clientNum, s.c_str())) { _active = false; - log_debug("WebSocket is unresponsive; closing"); + log_debug_to(Uart0, "WebSocket is unresponsive; closing"); return false; } return true; @@ -95,7 +94,7 @@ namespace WebUI { int stat = _server->canSend(_clientNum); if (stat < 0) { _active = false; - log_debug("WebSocket is dead; closing"); + log_debug_to(Uart0, "WebSocket is dead; closing"); return; } if (stat == 0) { @@ -195,18 +194,18 @@ namespace WebUI { void WSChannels::handleEvent(WebSocketsServer* server, uint8_t num, uint8_t type, uint8_t* payload, size_t length) { switch (type) { case WStype_DISCONNECTED: - log_debug("WebSocket disconnect " << num); + log_debug_to(Uart0, "WebSocket disconnect " << num); WSChannels::removeChannel(num); break; case WStype_CONNECTED: { WSChannel* wsChannel = new WSChannel(server, num); if (!wsChannel) { - log_error("Creating WebSocket channel failed"); + log_error_to(Uart0, "Creating WebSocket channel failed"); } else { std::string uri((char*)payload, length); IPAddress ip = server->remoteIP(num); - log_debug("WebSocket " << num << " from " << ip << " uri " << uri); + log_debug_to(Uart0, "WebSocket " << num << " from " << ip << " uri " << uri); _lastWSChannel = wsChannel; allChannels.registration(wsChannel); @@ -238,19 +237,19 @@ namespace WebUI { void WSChannels::handlev3Event(WebSocketsServer* server, uint8_t num, uint8_t type, uint8_t* payload, size_t length) { switch (type) { case WStype_DISCONNECTED: - log_debug("WebSocket disconnect " << num); + printf("WebSocket disconnect %d\n", num); WSChannels::removeChannel(num); break; case WStype_CONNECTED: { - log_debug("WStype_Connected"); + log_debug_to(Uart0, "WStype_Connected"); WSChannel* wsChannel = new WSChannel(server, num); if (!wsChannel) { - log_error("Creating WebSocket channel failed"); + log_error_to(Uart0, "Creating WebSocket channel failed"); } else { std::string uri((char*)payload, length); IPAddress ip = server->remoteIP(num); - log_debug("WebSocket " << num << " from " << ip << " uri " << uri); + log_debug_to(Uart0, "WebSocket " << num << " from " << ip << " uri " << uri); _lastWSChannel = wsChannel; allChannels.registration(wsChannel); @@ -276,7 +275,6 @@ namespace WebUI { case WStype_TEXT: try { std::string msg = (const char*)payload; - //log_debug("WSv3Channels::handleEvent WStype_TEXT:" << msg) if (msg.rfind("PING:", 0) == 0) { std::string response("PING:60000:60000"); _wsChannels.at(num)->sendTXT(response); diff --git a/FluidNC/src/WebUI/WifiConfig.cpp b/FluidNC/src/WebUI/WifiConfig.cpp index 7bf85c294..0b33943a8 100644 --- a/FluidNC/src/WebUI/WifiConfig.cpp +++ b/FluidNC/src/WebUI/WifiConfig.cpp @@ -560,7 +560,18 @@ namespace WebUI { s << "no"; #endif s << " # webcommunication: Sync: "; - s << std::to_string(Web_Server::port() + 1) + ":"; + s << std::to_string(Web_Server::port() + 1); +#if 0 + // If we omit the explicit IP address for the websocket, + // WebUI will use the same IP address that it uses for + // HTTP, with the port number as above. That is better + // than providing an explicit address, because if the WiFi + // drops and comes back up again, DHCP might assign a + // different IP address so the one provided below would no + // longer work. But if we are using an MDNS address like + // fluidnc.local, a websocket reconnection will succeed + // because MDNS will offer the new IP address. + s << ":"; switch (WiFi.getMode()) { case WIFI_AP: s << IP_string(WiFi.softAPIP()); @@ -575,6 +586,7 @@ namespace WebUI { s << "0.0.0.0"; break; } +#endif s << " # hostname:"; s << WiFi.getHostname(); if (WiFi.getMode() == WIFI_AP) { @@ -616,14 +628,26 @@ namespace WebUI { */ static void WiFiEvent(WiFiEvent_t event) { + static bool disconnect_seen = false; switch (event) { case SYSTEM_EVENT_STA_GOT_IP: break; case SYSTEM_EVENT_STA_DISCONNECTED: - log_info("WiFi Disconnected"); + if (!disconnect_seen) { + log_info_to(Uart0, "WiFi Disconnected"); + disconnect_seen = true; + } + break; + case SYSTEM_EVENT_STA_START: + break; + case SYSTEM_EVENT_STA_STOP: + break; + case SYSTEM_EVENT_STA_CONNECTED: + disconnect_seen = false; + log_info_to(Uart0, "WiFi STA Connected"); break; default: - //log_info("WiFi event:" << event); + log_debug_to(Uart0, "WiFi event: " << (int)event); break; } } @@ -692,6 +716,7 @@ namespace WebUI { WiFi.mode(WIFI_STA); WiFi.setMinSecurity(static_cast(_sta_min_security->get())); WiFi.setScanMethod(_fast_scan->get() ? WIFI_FAST_SCAN : WIFI_ALL_CHANNEL_SCAN); + WiFi.setAutoReconnect(true); //Get parameters for STA //password const char* password = _sta_password->get(); From e6e00db101ef457586b37d350d152e8f2b938d77 Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sat, 19 Oct 2024 13:45:05 -1000 Subject: [PATCH 25/30] No-jitter I2S engine --- FluidNC/esp32/i2s_engine.c | 251 +++++++++++++++------------ FluidNC/esp32/rmt_engine.c | 21 ++- FluidNC/esp32/timed_engine.c | 27 ++- FluidNC/include/Driver/step_engine.h | 12 +- FluidNC/src/Stepping.cpp | 23 +-- FluidNC/src/Stepping.h | 3 +- 6 files changed, 200 insertions(+), 137 deletions(-) diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index 55e701a9d..4dae502f9 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -1,10 +1,18 @@ // Copyright (c) 2024 - Mitch Bradley // Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. -// Stepping engine that uses the I2S FIFO +// Stepping engine that uses the I2S FIFO. An interrupt service routine runs when +// the FIFO is below a set threshold. The ISR pushes samples into the FIFO, representing +// step pulses and inter-pulse delays. There are variables for the value to push for +// a pulse, the number of samples for that pulse, the value to push to the delay, and +// the number of samples for the delay. When the delay is done, the ISR calls the Stepper +// pulse_func to determine the new values of those variables. The FIFO lets the ISR stay +// just far enough ahead so the information is always ready, but not so far ahead to cause +// latency problems. #include "Driver/step_engine.h" #include "Driver/i2s_out.h" +#include "Driver/StepTimer.h" #include "hal/i2s_hal.h" #include @@ -14,7 +22,6 @@ #include // IRAM_ATTR #include -// #include #include #include @@ -22,6 +29,8 @@ #include #include "Driver/fluidnc_gpio.h" +#include "esp_intr_alloc.h" + /* 16-bit mode: 1000000 usec / ((160000000 Hz) / 10 / 2) x 16 bit/pulse x 2(stereo) = 4 usec/pulse */ /* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ const uint32_t I2S_OUT_USEC_PER_PULSE = 2; @@ -50,54 +59,12 @@ static std::atomic i2s_out_port_data = ATOMIC_VAR_INIT(0); const int I2S_SAMPLE_SIZE = 4; /* 4 bytes, 32 bits per sample */ -// inner lock -static portMUX_TYPE i2s_out_spinlock = portMUX_INITIALIZER_UNLOCKED; -#define I2S_OUT_ENTER_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portENTER_CRITICAL_ISR(&i2s_out_spinlock); \ - } else { \ - portENTER_CRITICAL(&i2s_out_spinlock); \ - } \ - } while (0) -#define I2S_OUT_EXIT_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portEXIT_CRITICAL_ISR(&i2s_out_spinlock); \ - } else { \ - portEXIT_CRITICAL(&i2s_out_spinlock); \ - } \ - } while (0) -#define I2S_OUT_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_spinlock) -#define I2S_OUT_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_spinlock) - static int i2s_out_initialized = 0; static pinnum_t i2s_out_ws_pin = 255; static pinnum_t i2s_out_bck_pin = 255; static pinnum_t i2s_out_data_pin = 255; -// outer lock -static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; -#define I2S_OUT_PULSER_ENTER_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ - } else { \ - portENTER_CRITICAL(&i2s_out_pulser_spinlock); \ - } \ - } while (0) -#define I2S_OUT_PULSER_EXIT_CRITICAL() \ - do { \ - if (xPortInIsrContext()) { \ - portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock); \ - } else { \ - portEXIT_CRITICAL(&i2s_out_pulser_spinlock); \ - } \ - } while (0) -#define I2S_OUT_PULSER_ENTER_CRITICAL_ISR() portENTER_CRITICAL_ISR(&i2s_out_pulser_spinlock) -#define I2S_OUT_PULSER_EXIT_CRITICAL_ISR() portEXIT_CRITICAL_ISR(&i2s_out_pulser_spinlock) - #if I2S_OUT_NUM_BITS == 16 # define DATA_SHIFT 16 #else @@ -108,11 +75,7 @@ static portMUX_TYPE i2s_out_pulser_spinlock = portMUX_INITIALIZER_UNLOCKED; // Internal functions // void IRAM_ATTR i2s_out_push_fifo(int count) { -#if 0 - uint32_t portData = ATOMIC_LOAD(&i2s_out_port_data) << DATA_SHIFT; -#else uint32_t portData = i2s_out_port_data << DATA_SHIFT; -#endif for (int i = 0; i < count; i++) { I2S0.fifo_wr = portData; } @@ -158,8 +121,6 @@ static int i2s_out_gpio_shiftout(uint32_t port_data) { } static int i2s_out_stop() { - I2S_OUT_ENTER_CRITICAL(); - // stop TX module i2s_ll_tx_stop(&I2S0); @@ -179,12 +140,6 @@ static int i2s_out_stop() { uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value i2s_out_gpio_shiftout(port_data); -#if 0 - //clear pending interrupt - i2s_ll_clear_intr_status(&I2S0, i2s_ll_get_intr_status(&I2S0)); -#endif - - I2S_OUT_EXIT_CRITICAL(); return 0; } @@ -193,7 +148,6 @@ static int i2s_out_start() { return -1; } - I2S_OUT_ENTER_CRITICAL(); // Transmit recovery data to 74HC595 uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value i2s_out_gpio_shiftout(port_data); @@ -208,19 +162,12 @@ static int i2s_out_start() { i2s_ll_tx_set_chan_mod(&I2S0, I2S_CHANNEL_FMT_ONLY_LEFT); i2s_ll_tx_stop_on_fifo_empty(&I2S0, true); - -#if 0 - i2s_ll_clear_intr_status(&I2S0, 0xFFFFFFFF); -#endif - i2s_ll_tx_start(&I2S0); // Wait for the first FIFO data to prevent the unintentional generation of 0 data delay_us(20); i2s_ll_tx_stop_on_fifo_empty(&I2S0, false); - I2S_OUT_EXIT_CRITICAL(); - return 0; } @@ -228,11 +175,9 @@ static int i2s_out_start() { // External funtions // void i2s_out_delay() { - I2S_OUT_PULSER_ENTER_CRITICAL(); // Depending on the timing, it may not be reflected immediately, // so wait twice as long just in case. delay_us(I2S_OUT_USEC_PER_PULSE * 2); - I2S_OUT_PULSER_EXIT_CRITICAL(); } void IRAM_ATTR i2s_out_write(pinnum_t pin, uint8_t val) { @@ -249,12 +194,8 @@ uint8_t i2s_out_read(pinnum_t pin) { return !!(port_data & (1 << pin)); } -// -// Initialize function (external function) -// int i2s_out_init(i2s_out_init_t* init_param) { if (i2s_out_initialized) { - // already initialized return -1; } @@ -370,17 +311,9 @@ int i2s_out_init(i2s_out_init_t* init_param) { i2s_ll_mclk_div_t first_div = { 2, 3, 47 }; // { N, b, a } i2s_ll_tx_set_clk(&I2S0, &first_div); - volatile void* ptr = &I2S0; - uint32_t value; - - delay_us(20); - value = *(uint32_t*)(ptr + 0xac); - i2s_ll_mclk_div_t div = { 2, 32, 16 }; // b/a = 0.5 i2s_ll_tx_set_clk(&I2S0, &div); - value = *(uint32_t*)(ptr + 0xac); - // Bit clock configuration bit in transmitter mode. // fbck = fi2s / tx_bck_div_num = (160 MHz / 5) / 2 = 16 MHz i2s_ll_tx_set_bck_div_num(&I2S0, 2); @@ -398,11 +331,111 @@ int i2s_out_init(i2s_out_init_t* init_param) { return 0; } +// Interface to step engine + static uint32_t _pulse_counts = 2; static uint32_t _dir_delay_us; -// Convert the delays from microseconds to a number of I2S frame -static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us) { +// The key FIFO parameters are FIFO_THRESHOLD and FIFO_RELOAD +// Their sum must be less than FIFO_LENGTH (64 for ESP32). +// - FIFO_THRESHOLD is the level at which the interrupt fires. +// If it is too low, you risk FIFO underflow. Higher values +// allow more leeway for interrupt latency, but increase the +// latency between the software step generation and the appearance +// of step pulses at the driver. +// - FIFO_RELOAD is the number of entries that each ISR invocation +// pushes into the FIFO. Larger values of FIFO_RELOAD decrease +// the number of times that the ISR runs, while smaller values +// decrease the step generation latency. +// - With an I2S frame clock of 500 kHz, FIFO_THRESHOLD = 16, +// FIFO_RELOAD = 8, the step latency is about 24 us. That +// is about half of the modulation period of a laser that +// is modulated at 20 kHZ. + +#define FIFO_LENGTH (I2S_TX_DATA_NUM + 1) +#define FIFO_THRESHOLD (FIFO_LENGTH / 4) +#define FIFO_REMAINING (FIFO_LENGTH - FIFO_THRESHOLD) +#define FIFO_RELOAD 8 + +bool (*_pulse_func)(); + +static uint32_t _remaining_pulse_counts = 0; +static uint32_t _remaining_delay_counts = 0; + +static uint32_t _pulse_data; +static uint32_t _delay_counts = 40; +static uint32_t _tick_divisor; + +static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { + _delay_counts = ticks / _tick_divisor; +} + +static void IRAM_ATTR start_timer() { + i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 1); + i2s_ll_clear_intr_status(&I2S0, I2S_PUT_DATA_INT_CLR); +} +static void IRAM_ATTR stop_timer() { + i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 0); +} + +static void IRAM_ATTR i2s_isr() { + // gpio_write(12, 1); // For debugging + + // Keeping local copies of this information speeds up the ISR + uint32_t pulse_data = _pulse_data; + uint32_t delay_data = i2s_out_port_data; + uint32_t remaining_pulse_counts = _remaining_pulse_counts; + uint32_t remaining_delay_counts = _remaining_delay_counts; + + int i = FIFO_RELOAD; + do { + if (remaining_pulse_counts) { + I2S0.fifo_wr = pulse_data; + --i; + --remaining_pulse_counts; + } else if (remaining_delay_counts) { + I2S0.fifo_wr = delay_data; + --i; + --remaining_delay_counts; + } else { + _pulse_func(); + + // Reload from variables that could have been modified by pulse_func + pulse_data = _pulse_data; + delay_data = i2s_out_port_data; + + remaining_pulse_counts = pulse_data == delay_data ? 0 : _pulse_counts; + remaining_delay_counts = _delay_counts - _pulse_counts; + } + } while (i); + + // Save the counts back to the variables + _remaining_pulse_counts = remaining_pulse_counts; + _remaining_delay_counts = remaining_delay_counts; + + // Clear the interrupt after pushing new data into the FIFO. If you clear + // it before, the interrupt will re-fire back because the FIFO is still + // below the threshold. + i2s_ll_clear_intr_status(&I2S0, I2S_PUT_DATA_INT_CLR); + + // gpio_write(12, 0); +} + +static void i2s_fifo_intr_setup() { + I2S0.fifo_conf.tx_data_num = FIFO_THRESHOLD; + esp_intr_alloc_intrstatus(ETS_I2S0_INTR_SOURCE, + ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL3, + (uint32_t)i2s_ll_get_intr_status_reg(&I2S0), + I2S_PUT_DATA_INT_CLR_M, + i2s_isr, + NULL, + NULL); +} + +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us, uint32_t frequency, bool (*callback)(void)) { + _pulse_func = callback; + i2s_fifo_intr_setup(); + if (pulse_us < I2S_OUT_USEC_PER_PULSE) { pulse_us = I2S_OUT_USEC_PER_PULSE; } @@ -411,6 +444,12 @@ static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us) { } _dir_delay_us = dir_delay_us; _pulse_counts = (pulse_us + I2S_OUT_USEC_PER_PULSE - 1) / I2S_OUT_USEC_PER_PULSE; + _tick_divisor = frequency * I2S_OUT_USEC_PER_PULSE / 1000000; + + _remaining_pulse_counts = 0; + _remaining_delay_counts = 0; + + // gpio_mode(12, 0, 1, 0, 0, 0); return _pulse_counts * I2S_OUT_USEC_PER_PULSE; } @@ -426,21 +465,6 @@ static IRAM_ATTR void set_dir_pin(int pin, int level) { i2s_out_write(pin, level); } -uint32_t new_port_data; - -static IRAM_ATTR void start_step() { - new_port_data = i2s_out_port_data; -} - -static IRAM_ATTR void set_step_pin(int pin, int level) { - uint32_t bit = 1 << pin; - if (level) { - new_port_data |= bit; - } else { - new_port_data &= ~bit; - } -} - // For direction changes, we push one sample to the FIFO // and busy-wait for the delay. If the delay is short enough, // it might be possible to use the same multiple-sample trick @@ -451,22 +475,22 @@ static IRAM_ATTR void finish_dir() { delay_us(_dir_delay_us); } -// After all the desired values have been set with set_pin(), -// push _pulse_counts copies of the memory variable to the -// I2S FIFO, thus creating a pulse of the desired length. -static IRAM_ATTR void finish_step() { - if (new_port_data == i2s_out_port_data) { - return; - } - for (int i = 0; i < _pulse_counts; i++) { - I2S0.fifo_wr = new_port_data; +static void IRAM_ATTR start_step() { + _pulse_data = i2s_out_port_data; +} + +static IRAM_ATTR void set_step_pin(int pin, int level) { + uint32_t bit = 1 << pin; + if (level) { + _pulse_data |= bit; + } else { + _pulse_data &= ~bit; } - // There is no need for multiple "step off" samples since the timer will not fire - // until the next time for a pulse. - I2S0.fifo_wr = i2s_out_port_data; } -static IRAM_ATTR int start_unstep() { +static void IRAM_ATTR finish_step() {} + +static int IRAM_ATTR start_unstep() { return 1; } @@ -478,7 +502,7 @@ static uint32_t max_pulses_per_sec() { } // clang-format off -step_engine_t engine = { +step_engine_t i2s_engine = { "I2S", init_engine, init_step_pin, @@ -489,7 +513,10 @@ step_engine_t engine = { finish_step, start_unstep, finish_unstep, - max_pulses_per_sec + max_pulses_per_sec, + set_timer_ticks, + start_timer, + stop_timer }; - -REGISTER_STEP_ENGINE(I2S, &engine); +// clang-format on +REGISTER_STEP_ENGINE(I2S, &i2s_engine); diff --git a/FluidNC/esp32/rmt_engine.c b/FluidNC/esp32/rmt_engine.c index e876ab930..b5e1e1fad 100644 --- a/FluidNC/esp32/rmt_engine.c +++ b/FluidNC/esp32/rmt_engine.c @@ -6,6 +6,7 @@ #include "Driver/step_engine.h" #include "Driver/fluidnc_gpio.h" +#include "Driver/StepTimer.h" #include #include #include // IRAM_ATTR @@ -13,7 +14,8 @@ static uint32_t _pulse_delay_us; static uint32_t _dir_delay_us; -static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us) { +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us, uint32_t frequency, bool (*callback)(void)) { + stepTimerInit(frequency, callback); _dir_delay_us = dir_delay_us; _pulse_delay_us = pulse_delay_us; return _pulse_delay_us; @@ -113,6 +115,18 @@ static uint32_t max_pulses_per_sec() { return pps; } +static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { + stepTimerSetTicks(ticks); +} + +static void IRAM_ATTR start_timer() { + stepTimerStart(); +} + +static void IRAM_ATTR stop_timer() { + stepTimerStop(); +} + // clang-format off static step_engine_t engine = { "RMT", @@ -125,7 +139,10 @@ static step_engine_t engine = { finish_step, start_unstep, finish_unstep, - max_pulses_per_sec + max_pulses_per_sec, + set_timer_ticks, + start_timer, + stop_timer }; REGISTER_STEP_ENGINE(RMT, &engine); diff --git a/FluidNC/esp32/timed_engine.c b/FluidNC/esp32/timed_engine.c index 5bc74fce2..b1e6b46db 100644 --- a/FluidNC/esp32/timed_engine.c +++ b/FluidNC/esp32/timed_engine.c @@ -6,13 +6,15 @@ #include "Driver/step_engine.h" #include "Driver/fluidnc_gpio.h" #include "Driver/delay_usecs.h" +#include "Driver/StepTimer.h" #include #include // IRAM_ATTR static uint32_t _pulse_delay_us; static uint32_t _dir_delay_us; -static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us) { +static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_delay_us, uint32_t frequency, bool (*callback)(void)) { + stepTimerInit(frequency, callback); _dir_delay_us = dir_delay_us; _pulse_delay_us = pulse_delay_us; return _pulse_delay_us; @@ -32,7 +34,7 @@ static void IRAM_ATTR finish_dir() { delay_us(_dir_delay_us); } -static IRAM_ATTR void start_step() {} +static void IRAM_ATTR start_step() {} // Instead of waiting here for the step end time, we mark when the // step pulse should end, then return. The stepper code can then do @@ -42,19 +44,31 @@ static void IRAM_ATTR finish_step() { _stepPulseEndTime = usToEndTicks(_pulse_delay_us); } -static IRAM_ATTR int start_unstep() { +static int IRAM_ATTR start_unstep() { spinUntil(_stepPulseEndTime); return 0; } // This is a noop because each gpio_write() takes effect immediately, // so there is no need to commit multiple GPIO changes. -static IRAM_ATTR void finish_unstep() {} +static void IRAM_ATTR finish_unstep() {} static uint32_t max_pulses_per_sec() { return 1000000 / (2 * _pulse_delay_us); } +static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { + stepTimerSetTicks(ticks); +} + +static void IRAM_ATTR start_timer() { + stepTimerStart(); +} + +static void IRAM_ATTR stop_timer() { + stepTimerStop(); +} + // clang-format off static step_engine_t engine = { "Timed", @@ -67,7 +81,10 @@ static step_engine_t engine = { finish_step, start_unstep, finish_unstep, - max_pulses_per_sec + max_pulses_per_sec, + set_timer_ticks, + start_timer, + stop_timer }; REGISTER_STEP_ENGINE(Timed, &engine); diff --git a/FluidNC/include/Driver/step_engine.h b/FluidNC/include/Driver/step_engine.h index 82431337f..f0b4e27de 100644 --- a/FluidNC/include/Driver/step_engine.h +++ b/FluidNC/include/Driver/step_engine.h @@ -9,6 +9,7 @@ #pragma once #include +#include typedef struct step_engine { const char* name; @@ -16,7 +17,7 @@ typedef struct step_engine { // Prepare the engine for use // The return value is the actual pulse delay according to the // characteristics of the engine. - uint32_t (*init)(uint32_t dir_delay_us, uint32_t pulse_delay_us); + uint32_t (*init)(uint32_t dir_delay_us, uint32_t pulse_delay_us, uint32_t frequency, bool (*fn)(void)); // Setup the step pin, returning a number to identify it. // In many cases, the return value is the same as pin, but some step @@ -52,6 +53,15 @@ typedef struct step_engine { // pulse_delay_us, and other characteristics of this stepping engine uint32_t (*max_pulses_per_sec)(); + // Set the period to the next pulse event in ticks of the stepping timer + void (*set_timer_ticks)(uint32_t ticks); + + // Start the pulse event timer + void (*start_timer)(); + + // Stop the pulse event timer + void (*stop_timer)(); + // Link to next engine in the list of registered stepping engines struct step_engine* link; } step_engine_t; diff --git a/FluidNC/src/Stepping.cpp b/FluidNC/src/Stepping.cpp index d3e2ef12c..eb166668d 100644 --- a/FluidNC/src/Stepping.cpp +++ b/FluidNC/src/Stepping.cpp @@ -36,8 +36,8 @@ namespace Machine { const EnumItem stepTypes[] = { { Stepping::TIMED, "Timed" }, { Stepping::RMT_ENGINE, "RMT" }, - { Stepping::I2S_STATIC, "I2S_static" }, - { Stepping::I2S_STREAM, "I2S_stream" }, + { Stepping::I2S_STATIC, "I2S_STATIC" }, + { Stepping::I2S_STREAM, "I2S_STREAM" }, EnumItem(Stepping::RMT_ENGINE) }; void Stepping::afterParse() { @@ -51,15 +51,10 @@ namespace Machine { log_info("Stepping:" << stepTypes[_engine].name << " Pulse:" << _pulseUsecs << "us Dsbl Delay:" << _disableDelayUsecs << "us Dir Delay:" << _directionDelayUsecs << "us Idle Delay:" << _idleMsecs << "ms"); - uint32_t actual = step_engine->init(_directionDelayUsecs, _pulseUsecs); + uint32_t actual = step_engine->init(_directionDelayUsecs, _pulseUsecs, fStepperTimer, Stepper::pulse_func); if (actual != _pulseUsecs) { log_warn("stepping/pulse_us adjusted to " << actual); } - // Prepare stepping interrupt callbacks. The one that is actually - // used is determined by timerStart() and timerStop() - - // Setup a timer for direct stepping - stepTimerInit(fStepperTimer, Stepper::pulse_func); // Register pulse_func with the I2S subsystem // This could be done via the linker. @@ -184,22 +179,20 @@ void Stepping::reset() {} void Stepping::beginLowLatency() {} void Stepping::endLowLatency() {} -// Called only from step() -void IRAM_ATTR Stepping::waitDirection() {} - // Called only from Stepper::pulse_func when a new segment is loaded // The argument is in units of ticks of the timer that generates ISRs -void IRAM_ATTR Stepping::setTimerPeriod(uint16_t timerTicks) { - stepTimerSetTicks((uint32_t)timerTicks); +void IRAM_ATTR Stepping::setTimerPeriod(uint32_t ticks) { + step_engine->set_timer_ticks((uint32_t)ticks); } // Called only from Stepper::wake_up which is not used in ISR context void Stepping::startTimer() { - stepTimerStart(); + step_engine->start_timer(); } + // Called only from Stepper::stop_stepping, used in both ISR and foreground contexts void IRAM_ATTR Stepping::stopTimer() { - stepTimerStop(); + step_engine->stop_timer(); } void Stepping::group(Configuration::HandlerBase& handler) { diff --git a/FluidNC/src/Stepping.h b/FluidNC/src/Stepping.h index fa67a8e2a..fbf7a48ab 100644 --- a/FluidNC/src/Stepping.h +++ b/FluidNC/src/Stepping.h @@ -5,7 +5,6 @@ #pragma once #include "Configuration/Configurable.h" -#include "Driver/StepTimer.h" #include "Driver/step_engine.h" namespace Machine { @@ -89,7 +88,7 @@ namespace Machine { static uint32_t maxPulsesPerSec(); // Timers - static void setTimerPeriod(uint16_t timerTicks); + static void setTimerPeriod(uint32_t timerTicks); static void startTimer(); static void stopTimer(); From 59f9b179f9d85f811a4456a45da3686dc62f030d Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Fri, 25 Oct 2024 18:08:07 -1000 Subject: [PATCH 26/30] Fixed bug in i2s engine that broke homing The problem was that pulse_func() can do nothing if it is not awake, and in that case, the _pulse_data variable was holding old data. --- FluidNC/esp32/i2s_engine.c | 69 ++++++++++-------------------- FluidNC/include/Driver/i2s_out.h | 16 +------ FluidNC/src/Pins/I2SOPinDetail.cpp | 1 - FluidNC/src/Stepper.cpp | 4 +- 4 files changed, 25 insertions(+), 65 deletions(-) diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index 4dae502f9..4be514616 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -31,33 +31,10 @@ #include "esp_intr_alloc.h" -/* 16-bit mode: 1000000 usec / ((160000000 Hz) / 10 / 2) x 16 bit/pulse x 2(stereo) = 4 usec/pulse */ /* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ const uint32_t I2S_OUT_USEC_PER_PULSE = 2; -// The library routines are not in IRAM so they can crash when called from FLASH -// The GCC intrinsic versions which are prefixed with __ are compiled inline -#define USE_INLINE_ATOMIC - -#ifdef USE_INLINE_ATOMIC -# define MEMORY_MODEL_FETCH __ATOMIC_RELAXED -# define MEMORY_MODEL_STORE __ATOMIC_RELAXED -# define ATOMIC_LOAD(var) __atomic_load_n(var, MEMORY_MODEL_FETCH) -# define ATOMIC_STORE(var, val) __atomic_store_n(var, val, MEMORY_MODEL_STORE) -# define ATOMIC_FETCH_AND(var, val) __atomic_fetch_and(var, val, MEMORY_MODEL_FETCH) -# define ATOMIC_FETCH_OR(var, val) __atomic_fetch_or(var, val, MEMORY_MODEL_FETCH) static uint32_t i2s_out_port_data = 0; -#else -# include -# define ATOMIC_LOAD(var) atomic_load(var) -# define ATOMIC_STORE(var, val) atomic_store(var, val) -# define ATOMIC_FETCH_AND(var, val) atomic_fetch_and(var, val) -# define ATOMIC_FETCH_OR(var, val) atomic_fetch_or(var, val) -static std::atomic i2s_out_port_data = ATOMIC_VAR_INIT(0); - -#endif - -const int I2S_SAMPLE_SIZE = 4; /* 4 bytes, 32 bits per sample */ static int i2s_out_initialized = 0; @@ -65,21 +42,9 @@ static pinnum_t i2s_out_ws_pin = 255; static pinnum_t i2s_out_bck_pin = 255; static pinnum_t i2s_out_data_pin = 255; -#if I2S_OUT_NUM_BITS == 16 -# define DATA_SHIFT 16 -#else -# define DATA_SHIFT 0 -#endif - // // Internal functions // -void IRAM_ATTR i2s_out_push_fifo(int count) { - uint32_t portData = i2s_out_port_data << DATA_SHIFT; - for (int i = 0; i < count; i++) { - I2S0.fifo_wr = portData; - } -} static inline void i2s_out_reset_tx_rx() { i2s_ll_tx_reset(&I2S0); @@ -137,7 +102,7 @@ static int i2s_out_stop() { gpio_write(i2s_out_bck_pin, 0); // Transmit recovery data to 74HC595 - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value + uint32_t port_data = i2s_out_port_data; // current expanded port value i2s_out_gpio_shiftout(port_data); return 0; @@ -149,7 +114,7 @@ static int i2s_out_start() { } // Transmit recovery data to 74HC595 - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); // current expanded port value + uint32_t port_data = i2s_out_port_data; // current expanded port value i2s_out_gpio_shiftout(port_data); // Attach I2S to specified GPIO pin @@ -183,14 +148,14 @@ void i2s_out_delay() { void IRAM_ATTR i2s_out_write(pinnum_t pin, uint8_t val) { uint32_t bit = 1 << pin; if (val) { - ATOMIC_FETCH_OR(&i2s_out_port_data, bit); + i2s_out_port_data |= bit; } else { - ATOMIC_FETCH_AND(&i2s_out_port_data, ~bit); + i2s_out_port_data &= ~bit; } } uint8_t i2s_out_read(pinnum_t pin) { - uint32_t port_data = ATOMIC_LOAD(&i2s_out_port_data); + uint32_t port_data = i2s_out_port_data; return !!(port_data & (1 << pin)); } @@ -199,7 +164,7 @@ int i2s_out_init(i2s_out_init_t* init_param) { return -1; } - ATOMIC_STORE(&i2s_out_port_data, init_param->init_val); + i2s_out_port_data = init_param->init_val; // To make sure hardware is enabled before any hardware register operations. periph_module_reset(PERIPH_I2S0_MODULE); @@ -367,12 +332,18 @@ static uint32_t _delay_counts = 40; static uint32_t _tick_divisor; static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { - _delay_counts = ticks / _tick_divisor; + if (ticks) { + _delay_counts = ticks / _tick_divisor; + } } static void IRAM_ATTR start_timer() { - i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 1); - i2s_ll_clear_intr_status(&I2S0, I2S_PUT_DATA_INT_CLR); + static bool once = true; + if (once) { + i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 1); + i2s_ll_clear_intr_status(&I2S0, I2S_PUT_DATA_INT_CLR); + once = false; + } } static void IRAM_ATTR stop_timer() { i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 0); @@ -382,8 +353,8 @@ static void IRAM_ATTR i2s_isr() { // gpio_write(12, 1); // For debugging // Keeping local copies of this information speeds up the ISR - uint32_t pulse_data = _pulse_data; uint32_t delay_data = i2s_out_port_data; + uint32_t pulse_data = _pulse_data; uint32_t remaining_pulse_counts = _remaining_pulse_counts; uint32_t remaining_delay_counts = _remaining_delay_counts; @@ -398,6 +369,10 @@ static void IRAM_ATTR i2s_isr() { --i; --remaining_delay_counts; } else { + // Set _pulse_data to a safe value in case pulse_func() does nothing, + // which can happen if it is not awake + _pulse_data = i2s_out_port_data; + _pulse_func(); // Reload from variables that could have been modified by pulse_func @@ -405,7 +380,7 @@ static void IRAM_ATTR i2s_isr() { delay_data = i2s_out_port_data; remaining_pulse_counts = pulse_data == delay_data ? 0 : _pulse_counts; - remaining_delay_counts = _delay_counts - _pulse_counts; + remaining_delay_counts = _delay_counts - remaining_pulse_counts; } } while (i); @@ -471,7 +446,7 @@ static IRAM_ATTR void set_dir_pin(int pin, int level) { // that we use for step pulses, but the optimizaton might not // be worthwhile since direction changes are infrequent. static IRAM_ATTR void finish_dir() { - i2s_out_push_fifo(1); + I2S0.fifo_wr = i2s_out_port_data; delay_us(_dir_delay_us); } diff --git a/FluidNC/include/Driver/i2s_out.h b/FluidNC/include/Driver/i2s_out.h index d757b0121..98415428a 100644 --- a/FluidNC/include/Driver/i2s_out.h +++ b/FluidNC/include/Driver/i2s_out.h @@ -11,14 +11,7 @@ extern "C" { #include "Driver/fluidnc_gpio.h" -/* Assert */ -#if defined(I2S_OUT_NUM_BITS) -# if (I2S_OUT_NUM_BITS != 16) && (I2S_OUT_NUM_BITS != 32) -# error "I2S_OUT_NUM_BITS should be 16 or 32" -# endif -#else -# define I2S_OUT_NUM_BITS 32 -#endif +#define I2S_OUT_NUM_BITS 32 // # define I2SO(n) (I2S_OUT_PIN_BASE + n) @@ -67,13 +60,6 @@ uint8_t i2s_out_read(pinnum_t pin); */ void i2s_out_write(pinnum_t pin, uint8_t val); -/* - Push the I2S output value that was constructed by a series of - i2s_out_write() calls to the I2S FIFO so it will be shifted out - count: Number of repetitions -*/ -void i2s_out_push_fifo(int count); - /* Dynamically delay until the Shift Register Pin changes according to the current I2S processing state and mode. diff --git a/FluidNC/src/Pins/I2SOPinDetail.cpp b/FluidNC/src/Pins/I2SOPinDetail.cpp index 3ab23bf8f..40e18042d 100644 --- a/FluidNC/src/Pins/I2SOPinDetail.cpp +++ b/FluidNC/src/Pins/I2SOPinDetail.cpp @@ -51,7 +51,6 @@ namespace Pins { _lastWrittenValue = high; i2s_out_write(_index, _inverted ^ (bool)high); - i2s_out_push_fifo(1); i2s_out_delay(); } } diff --git a/FluidNC/src/Stepper.cpp b/FluidNC/src/Stepper.cpp index 563844052..ecbd78a93 100644 --- a/FluidNC/src/Stepper.cpp +++ b/FluidNC/src/Stepper.cpp @@ -204,6 +204,7 @@ bool IRAM_ATTR Stepper::pulse_func() { auto n_axis = Axes::_numberAxis; Stepping::step(st.step_outbits, st.dir_outbits); + st.step_outbits = 0; // If there is no step segment, attempt to pop one from the stepper buffer if (st.exec_segment == NULL) { @@ -244,11 +245,10 @@ bool IRAM_ATTR Stepper::pulse_func() { protocol_send_event_from_ISR(&cycleStopEvent); awake = false; + Stepping::unstep(); return false; // Nothing to do but exit. } } - // Reset step out bits. - st.step_outbits = 0; for (int axis = 0; axis < n_axis; axis++) { // Execute step displacement profile by Bresenham line algorithm From 8e36c50349c06f001b120b2658cec9aeec154015 Mon Sep 17 00:00:00 2001 From: Stefan de Bruijn Date: Sun, 27 Oct 2024 17:42:36 +0100 Subject: [PATCH 27/30] VFD rework (#1324) * Fixed Generate_VCXProj * Moved VFD spindles to their own namespace and inheritance structure. TODO: Configuration. * Changed the factory a bit to ensure configurations still work. * This compiles. TODO: Loader script. * Changed names and made them consistent. This also removes them from IRAM. * Split out the VFDProtocol code into a separate file. * Added comment --------- Co-authored-by: Stefan de Bruijn --- FluidNC/src/Configuration/GenericFactory.h | 23 +- FluidNC/src/Platform.h | 1 + FluidNC/src/Spindles/DanfossSpindle.cpp | 157 ------- FluidNC/src/Spindles/DanfossSpindle.h | 83 ---- FluidNC/src/Spindles/H100Spindle.cpp | 177 -------- FluidNC/src/Spindles/H100Spindle.h | 31 -- FluidNC/src/Spindles/H2ASpindle.cpp | 133 ------ FluidNC/src/Spindles/H2ASpindle.h | 27 -- FluidNC/src/Spindles/HuanyangSpindle.cpp | 395 ----------------- FluidNC/src/Spindles/HuanyangSpindle.h | 32 -- FluidNC/src/Spindles/NowForeverSpindle.cpp | 263 ------------ FluidNC/src/Spindles/NowForeverSpindle.h | 27 -- FluidNC/src/Spindles/SiemensV20Spindle.h | 32 -- FluidNC/src/Spindles/Spindle.h | 7 +- .../Spindles/VFD/DanfossVLT2800Protocol.cpp | 167 ++++++++ .../src/Spindles/VFD/DanfossVLT2800Protocol.h | 83 ++++ FluidNC/src/Spindles/VFD/H100Protocol.cpp | 179 ++++++++ FluidNC/src/Spindles/VFD/H100Protocol.h | 30 ++ .../{H100Spindle.md => VFD/H100Protocol.md} | 0 FluidNC/src/Spindles/VFD/H2AProtocol.cpp | 136 ++++++ FluidNC/src/Spindles/VFD/H2AProtocol.h | 26 ++ .../{H2ASpindle.md => VFD/H2AProtocol.md} | 0 FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp | 399 ++++++++++++++++++ FluidNC/src/Spindles/VFD/HuanyangProtocol.h | 31 ++ .../src/Spindles/VFD/NowForeverProtocol.cpp | 267 ++++++++++++ FluidNC/src/Spindles/VFD/NowForeverProtocol.h | 26 ++ .../SiemensV20Protocol.cpp} | 206 +++++---- FluidNC/src/Spindles/VFD/SiemensV20Protocol.h | 32 ++ FluidNC/src/Spindles/VFD/VFDProtocol.cpp | 291 +++++++++++++ FluidNC/src/Spindles/VFD/VFDProtocol.h | 81 ++++ FluidNC/src/Spindles/VFD/YL620Protocol.cpp | 231 ++++++++++ FluidNC/src/Spindles/VFD/YL620Protocol.h | 26 ++ FluidNC/src/Spindles/VFDSpindle.cpp | 367 +++------------- FluidNC/src/Spindles/VFDSpindle.h | 71 +--- FluidNC/src/Spindles/YL620Spindle.cpp | 227 ---------- FluidNC/src/Spindles/YL620Spindle.h | 27 -- generate_vcxproj.py | 8 +- 37 files changed, 2214 insertions(+), 2085 deletions(-) delete mode 100644 FluidNC/src/Spindles/DanfossSpindle.cpp delete mode 100644 FluidNC/src/Spindles/DanfossSpindle.h delete mode 100644 FluidNC/src/Spindles/H100Spindle.cpp delete mode 100644 FluidNC/src/Spindles/H100Spindle.h delete mode 100644 FluidNC/src/Spindles/H2ASpindle.cpp delete mode 100644 FluidNC/src/Spindles/H2ASpindle.h delete mode 100644 FluidNC/src/Spindles/HuanyangSpindle.cpp delete mode 100644 FluidNC/src/Spindles/HuanyangSpindle.h delete mode 100644 FluidNC/src/Spindles/NowForeverSpindle.cpp delete mode 100644 FluidNC/src/Spindles/NowForeverSpindle.h delete mode 100644 FluidNC/src/Spindles/SiemensV20Spindle.h create mode 100644 FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.cpp create mode 100644 FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h create mode 100644 FluidNC/src/Spindles/VFD/H100Protocol.cpp create mode 100644 FluidNC/src/Spindles/VFD/H100Protocol.h rename FluidNC/src/Spindles/{H100Spindle.md => VFD/H100Protocol.md} (100%) create mode 100644 FluidNC/src/Spindles/VFD/H2AProtocol.cpp create mode 100644 FluidNC/src/Spindles/VFD/H2AProtocol.h rename FluidNC/src/Spindles/{H2ASpindle.md => VFD/H2AProtocol.md} (100%) create mode 100644 FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp create mode 100644 FluidNC/src/Spindles/VFD/HuanyangProtocol.h create mode 100644 FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp create mode 100644 FluidNC/src/Spindles/VFD/NowForeverProtocol.h rename FluidNC/src/Spindles/{SiemensV20Spindle.cpp => VFD/SiemensV20Protocol.cpp} (58%) create mode 100644 FluidNC/src/Spindles/VFD/SiemensV20Protocol.h create mode 100644 FluidNC/src/Spindles/VFD/VFDProtocol.cpp create mode 100644 FluidNC/src/Spindles/VFD/VFDProtocol.h create mode 100644 FluidNC/src/Spindles/VFD/YL620Protocol.cpp create mode 100644 FluidNC/src/Spindles/VFD/YL620Protocol.h delete mode 100644 FluidNC/src/Spindles/YL620Spindle.cpp delete mode 100644 FluidNC/src/Spindles/YL620Spindle.h diff --git a/FluidNC/src/Configuration/GenericFactory.h b/FluidNC/src/Configuration/GenericFactory.h index 0fb559a68..4e0c539d1 100644 --- a/FluidNC/src/Configuration/GenericFactory.h +++ b/FluidNC/src/Configuration/GenericFactory.h @@ -19,7 +19,7 @@ namespace Configuration { GenericFactory() = default; - GenericFactory(const GenericFactory&) = delete; + GenericFactory(const GenericFactory&) = delete; GenericFactory& operator=(const GenericFactory&) = delete; class BuilderBase { @@ -28,7 +28,7 @@ namespace Configuration { public: BuilderBase(const char* name) : name_(name) {} - BuilderBase(const BuilderBase& o) = delete; + BuilderBase(const BuilderBase& o) = delete; BuilderBase& operator=(const BuilderBase& o) = delete; virtual BaseType* create(const char* name) const = 0; @@ -59,6 +59,24 @@ namespace Configuration { BaseType* create(const char* name) const override { return new DerivedType(name); } }; + template + class DependentInstanceBuilder : public BuilderBase { + public: + explicit DependentInstanceBuilder(const char* name, bool autocreate = false) : BuilderBase(name) { + instance().registerBuilder(this); + if (autocreate) { + auto& objects = instance().objects_; + auto object = create(name); + objects.push_back(object); + } + } + + DerivedType* create(const char* name) const override { + auto dependency = new DependencyType(); + return new DerivedType(name, dependency); + } + }; + // This factory() method is used when there can be only one instance of the type, // as with a kinematics system. The variable that points to the instance must // be created externally and passed as an argument. @@ -75,6 +93,7 @@ namespace Configuration { handler.enterSection(inst->name(), inst); } } + // This factory() method is used when there can be multiple instances, // as with spindles and modules. A vector in the GenericFactory // singleton holds the derived type instances, so there is no need to diff --git a/FluidNC/src/Platform.h b/FluidNC/src/Platform.h index ca19f0852..6beec0fa9 100644 --- a/FluidNC/src/Platform.h +++ b/FluidNC/src/Platform.h @@ -9,5 +9,6 @@ #else # define WEAK_LINK +# define IRAM_ATTR #endif diff --git a/FluidNC/src/Spindles/DanfossSpindle.cpp b/FluidNC/src/Spindles/DanfossSpindle.cpp deleted file mode 100644 index 0ec931f96..000000000 --- a/FluidNC/src/Spindles/DanfossSpindle.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) 2024 - Jan Speckamp, whosmatt -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -/* - This is for a Danfoss VLT 2800 VFD based spindle to be controlled via RS485 Modbus RTU. - FluidNC imposes limitations (methods dont have access to full spindle state), while the Danfoss VFD expects the full state to be set with every command. - As an interim solution, the state of the spindle is cached in cachedSpindleState. - - Modbus setup of the VFD is covered in https://files.danfoss.com/download/Drives/doc_A_1_mg10s122.pdf - General setup of the VFD is covered in https://files.danfoss.com/download/Drives/doc_B_1_MG28E902.pdf -*/ - -#include "DanfossSpindle.h" - -#include - -#define READ_COIL 0x01 -#define READ_HR 0x03 - -#define WRITE_SINGLE_COIL 0x05 -#define WRITE_MULTIPLE_COIL 0x0F - -namespace Spindles { - void DanfossVLT2800::init() { - VFD::init(); - setupSpeeds(_maxFrequency); - } - - void IRAM_ATTR DanfossVLT2800::set_speed_command(uint32_t dev_speed, ModbusCommand& data) { - // Cache received speed - cachedSpindleState.speed = dev_speed; - - // Write speed and direction from cache to VFD - writeVFDState(cachedSpindleState, data); - } - - void DanfossVLT2800::direction_command(SpindleState mode, ModbusCommand& data) { - // Cache received direction - cachedSpindleState.state = mode; - - // Write speed and direction from cache to VFD - writeVFDState(cachedSpindleState, data); - } - - VFD::response_parser DanfossVLT2800::get_current_speed(ModbusCommand& data) { - data.tx_length = 6; // including automatically set client_id, excluding crc - data.rx_length = 3 + 2; // excluding crc - - // We write a full control word instead of setting individual coils - data.msg[1] = READ_HR; - data.msg[2] = 0x14; - data.msg[3] = 0x3b; // start register - data.msg[4] = 0x00; - data.msg[5] = 0x01; // no of points - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - // const uint8_t slave_addr = response[0] - // const uint8_t function = response[1] - // const uint8_t response_byte_count = response[2] - - uint16_t freq = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - vfd->_sync_dev_speed = freq; - return true; - }; - } - - VFD::response_parser DanfossVLT2800::get_status_ok(ModbusCommand& data) { - data.tx_length = 6; // including automatically set client_id, excluding crc - data.rx_length = 5; // excluding crc - - // Read out current state - data.msg[1] = READ_COIL; - data.msg[2] = 0x00; - data.msg[3] = 0x20; // Coil index 32 - data.msg[4] = 0x00; - data.msg[5] = 0x10; // Read 16 Bits - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - SpindleStatus status; - status.statusWord = int16_t(response[3]) | uint16_t(response[4] << 8); // See DanfossSpindle.h for structure - -#ifdef DEBUG_VFD - log_debug("Control ready:" << status.flags.control_ready); - log_debug("Drive ready:" << status.flags.drive_ready); - log_debug("Coasting stop:" << status.flags.warning); - log_debug("Trip status:" << status.flags.trip); - log_debug("Trip lock:" << status.flags.trip_lock); - log_debug("No warning/warning:" << status.flags.warning); - log_debug("Speed == ref:" << status.flags.speed_status); - log_debug("Local operation/serial communication control:" << status.flags.local_control); - log_debug("Outside frequency range:" << status.flags.freq_range_err); - log_debug("Motor running:" << status.flags.motor_running); - log_debug("Not used:" << status.flags.voltage_warn); - log_debug("Voltage warn:" << status.flags.voltage_warn); - log_debug("Current limit:" << status.flags.current_limit); - log_debug("Thermal warn:" << status.flags.thermal_warn); -#endif - log_error("TODO: actually check status bits and output potential errors"); - return true; - }; - } - - // The VLT2800 expects speed, direction and enable to be sent together at all times. - // This function uses a combined cached spindle state that includes the speed and sends it to the VFD. - void DanfossVLT2800::writeVFDState(combinedSpindleState spindle, ModbusCommand& data) { - SpindleControl cword; - cword.flags.coasting_stop = 1; - cword.flags.dc_braking_stop = 1; - cword.flags.quick_stop = 1; - cword.flags.freeze_freq = 1; - cword.flags.jog = 0; - cword.flags.reference_preset = 0; - cword.flags.setup_preset = 0; - cword.flags.output_46 = 0; - cword.flags.relay_01 = 0; - cword.flags.data_valid = 1; - cword.flags.reset = 0; - cword.flags.reverse = 0; - - switch (spindle.state) { - case SpindleState::Cw: - cword.flags.reverse = 0; - cword.flags.start_stop = 1; - break; - case SpindleState::Ccw: - cword.flags.reverse = 1; - cword.flags.start_stop = 1; - break; - case SpindleState::Disable: - cword.flags.start_stop = 0; - break; - default: - break; - } - - // Assemble packet: - data.tx_length = 11; - data.rx_length = 6; - - // We write a full control word instead of setting individual coils - data.msg[1] = WRITE_MULTIPLE_COIL; - data.msg[2] = 0x00; - data.msg[3] = 0x00; // start coil address - data.msg[4] = 0x00; - data.msg[5] = 0x20; // write length - data.msg[6] = 0x04; // payload byte count - - data.msg[7] = cword.controlWord & 0xFF; // MSB - data.msg[8] = cword.controlWord >> 8; // LSB - - data.msg[9] = spindle.speed & 0xFF; - data.msg[10] = spindle.speed >> 8; - } - namespace { - SpindleFactory::InstanceBuilder registration("DanfossVLT2800"); - } -} diff --git a/FluidNC/src/Spindles/DanfossSpindle.h b/FluidNC/src/Spindles/DanfossSpindle.h deleted file mode 100644 index d898451e7..000000000 --- a/FluidNC/src/Spindles/DanfossSpindle.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2024 - Jan Speckamp, whosmatt -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#pragma once - -#include "VFDSpindle.h" - -namespace Spindles { - class DanfossVLT2800 : public VFD { - union SpindleStatus { - struct { - bool control_ready : 1; // bit 00 = 0: "" | Bit = 1: "Control ready" - bool drive_ready : 1; // bit 01 = 0: "" | Bit = 1: "Drive ready" - bool coasting_stop : 1; // bit 02 = 0: "Coasting stop" | Bit = 1: "" - bool trip : 1; // bit 03 = 0: "No Trip" | Bit = 1: "Trip" - bool unused1 : 1; // bit 04 Not used - bool unused2 : 1; // bit 05 Not used - bool trip_lock : 1; // bit 06 = 0: "" | Bit = 1: "Trip lock" - bool warning : 1; // bit 07 = 0: "No warning" | Bit = 1: "Warning" - bool speed_status : 1; // bit 08 = 0: "Speed != ref." | Bit = 1: "Speed = ref." - bool local_control : 1; // bit 09 = 0: "Local control" | Bit = 1: "Ser. communi." - bool freq_range_err : 1; // bit 10 = 0: "Outside frequency range" | Bit = 1: "Frequency limit OK" - bool motor_running : 1; // bit 11 = 0: "" | Bit = 1: "Motor running" - bool unused3 : 1; // bit 12 Not used - bool voltage_warn : 1; // bit 13 = 0: "" | Bit = 1: "Voltage warn." - bool current_limit : 1; // bit 14 = 0: "" | Bit = 1: "Current limit" - bool thermal_warn : 1; // bit 15 = 0: "" | Bit = 1: "Thermal wan." - } flags; - uint16_t statusWord; - }; - - union SpindleControl { - struct { - uint8_t reference_preset : 2; // bit 00 = lsb of 2 bit value for preset reference selection - // bit 01 = msb - bool dc_braking_stop : 1; // bit 02 = 0 causes stop with dc brake - bool coasting_stop : 1; // bit 03 = 0 causes coasting stop - bool quick_stop : 1; // bit 04 = 0 causes quick stop - bool freeze_freq : 1; // bit 05 = 0 causes output frequency to be locked from inputs, stops still apply - bool start_stop : 1; // bit 06 = 1 causes motor start, 0 causes motor stop, standard ramp applies - bool reset : 1; // bit 07 = resets trip condition on change from 0 to 1 - bool jog : 1; // bit 08 = 1 switches to jogging (par. 213) - bool ramp_select : 1; // bit 09 = ramp selection: 0 = ramp 1 (par. 207-208), 1 = ramp 2 (par. 209-210) - bool data_valid : 1; // bit 10 = 0 causes entire control word to be ignored - bool relay_01 : 1; // bit 11 = 1 activates relay 01 - bool output_46 : 1; // bit 12 = 1 activates digital output on terminal 46 - uint8_t setup_preset : 2; // bit 13 = lsb of 2 bit value for setup selection when par. 004 multi setup is enabled - // bit 14 = msb - bool reverse : 1; // bit 15 = 1 causes reversing - } flags; - uint16_t controlWord; - }; - - protected: - // Unlike other VFDs, the VLT seems to take speed as int16_t, mapped to 0-200% of the configured maximum reference. - uint16_t _minFrequency = 0x0; // motor off (0% speed) - uint16_t _maxFrequency = 0x4000; // max speed the VFD will allow. 0x4000 = 100% for VLT2800 - - void direction_command(SpindleState mode, ModbusCommand& data) override; - void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - - response_parser initialization_sequence(int index, ModbusCommand& data) { return nullptr; }; - response_parser get_current_speed(ModbusCommand& data) override; - response_parser get_current_direction(ModbusCommand& data) override { return nullptr; }; - response_parser get_status_ok(ModbusCommand& data) override; - - bool safety_polling() const override { return true; } - - public: - DanfossVLT2800(const char* name) : VFD(name) {} - void init(); - - private: - struct combinedSpindleState { - SpindleState state; - uint32_t speed; - } cachedSpindleState; - - void writeVFDState(combinedSpindleState spindle, ModbusCommand& data); - - void parse_spindle_status(uint16_t statusword, SpindleStatus& status); - }; -} diff --git a/FluidNC/src/Spindles/H100Spindle.cpp b/FluidNC/src/Spindles/H100Spindle.cpp deleted file mode 100644 index a7c8557bb..000000000 --- a/FluidNC/src/Spindles/H100Spindle.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2020 - Stefan de Bruijn -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -/* - H100Spindle.cpp - - This is for a H100 VFD based spindle via RS485 Modbus. - - WARNING!!!! - VFDs are very dangerous. They have high voltages and are very powerful - Remove power before changing bits. -*/ - -#include "H100Spindle.h" - -#include // std::max -#include // IRAM_ATTR - -namespace Spindles { - void H100Spindle::direction_command(SpindleState mode, ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 6; - data.rx_length = 6; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x05; - data.msg[2] = 0x00; - - switch (mode) { - case SpindleState::Cw: //[01] [05] [00 49] [ff 00] -- forward run - data.msg[3] = 0x49; - data.msg[4] = 0xFF; - data.msg[5] = 0x00; - break; - case SpindleState::Ccw: //[01] [05] [00 4A] [ff 00] -- reverse run - data.msg[3] = 0x4A; - data.msg[4] = 0xFF; - data.msg[5] = 0x00; - break; - default: // SpindleState::Disable [01] [05] [00 4B] [ff 00] -- stop - data.msg[3] = 0x4B; - data.msg[4] = 0xFF; - data.msg[5] = 0x00; - break; - } - } - - void IRAM_ATTR H100Spindle::set_speed_command(uint32_t dev_speed, ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 6; - - if (dev_speed != 0 && (dev_speed < _minFrequency || dev_speed > _maxFrequency)) { - log_warn(name() << " requested freq " << (dev_speed) << " is outside of range (" << _minFrequency << "," << _maxFrequency << ")"); - } - -#ifdef DEBUG_VFD - log_debug("Setting VFD dev_speed to " << dev_speed); -#endif - - //[01] [06] [0201] [07D0] Set frequency to [07D0] = 200.0 Hz. (2000 is written!) - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x06; // Set register command - data.msg[2] = 0x02; - data.msg[3] = 0x01; - //data.msg[4] = dev_speed >> 8; - BC 11/24/21 - data.msg[4] = dev_speed >> 8; - data.msg[5] = dev_speed & 0xFF; - } - - // This gets data from the VFD. It does not set any values - VFD::response_parser H100Spindle::initialization_sequence(int index, ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 6; - data.rx_length = 5; - // Read F011 (min frequency) and F005 (max frequency): - // - // [03] [000B] [0001] gives [03] [02] [xxxx] (with 02 the result byte count). - // [03] [0005] [0001] gives [03] [02] [xxxx] (with 02 the result byte count). - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x03; // Read setting - data.msg[2] = 0x00; - // [3] = set below... - data.msg[4] = 0x00; // length - data.msg[5] = 0x01; - - if (index == -1) { - // Max frequency - data.msg[3] = 0x05; // PD005: max frequency the VFD will allow. Normally 400. - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t value = (response[3] << 8) | response[4]; - -#ifdef DEBUG_VFD - log_debug("VFD: Max frequency = " << value / 10 << "Hz " << value / 10 * 60 << "RPM"); -#endif - log_info("VFD: Max speed:" << (value / 10 * 60) << "rpm"); - - // Set current RPM value? Somewhere? - auto h100 = static_cast(vfd); - h100->_maxFrequency = value; - - return true; - }; - - } else if (index == -2) { - // Min Frequency - data.msg[3] = 0x0B; // PD011: frequency lower limit. Normally 0. - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t value = (response[3] << 8) | response[4]; - -#ifdef DEBUG_VFD - log_debug("VFD: Min frequency = " << value / 10 << "Hz " << value / 10 * 60 << "RPM"); -#endif - log_info("VFD: Min speed:" << (value / 10 * 60) << "rpm"); - - // Set current RPM value? Somewhere? - auto h100 = static_cast(vfd); - h100->_minFrequency = value; - - h100->updateRPM(); - - return true; - }; - } - - // Done. - return nullptr; - } - - void H100Spindle::updateRPM() { - if (_minFrequency > _maxFrequency) { - _minFrequency = _maxFrequency; - } - - if (_speeds.size() == 0) { - SpindleSpeed minRPM = _minFrequency * 60 / 10; - SpindleSpeed maxRPM = _maxFrequency * 60 / 10; - - shelfSpeeds(minRPM, maxRPM); - } - setupSpeeds(_maxFrequency); - _slop = std::max(_maxFrequency / 40, 1); - - log_info("VFD: VFD settings read: Freq range(" << _minFrequency << " , " << _maxFrequency << ")]"); - } - - VFD::response_parser H100Spindle::get_current_speed(ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - // [01] [04] [0000] [0002] -- output frequency - data.tx_length = 6; - data.rx_length = 7; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x04; - data.msg[2] = 0x00; - data.msg[3] = 0x00; // Output frequency - data.msg[4] = 0x00; - data.msg[5] = 0x02; - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - // 01 04 04 [freq 16] [set freq 16] [crc16] - uint16_t frequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - - // Store speed for synchronization - vfd->_sync_dev_speed = frequency; - return true; - }; - } - - // Configuration registration - namespace { - SpindleFactory::InstanceBuilder registration("H100"); - } -} diff --git a/FluidNC/src/Spindles/H100Spindle.h b/FluidNC/src/Spindles/H100Spindle.h deleted file mode 100644 index 7f93e4461..000000000 --- a/FluidNC/src/Spindles/H100Spindle.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2020 - Stefan de Bruijn -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#pragma once - -#include "VFDSpindle.h" - -namespace Spindles { - class H100Spindle : public VFD { - private: - int reg; - - protected: - uint16_t _minFrequency = 0; - uint16_t _maxFrequency = 4000; // H100 works with frequencies scaled by 10. - - void updateRPM(); - - void direction_command(SpindleState mode, ModbusCommand& data) override; - void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - - response_parser initialization_sequence(int index, ModbusCommand& data) override; - response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } - response_parser get_current_speed(ModbusCommand& data) override; - - bool use_delay_settings() const override { return false; } - - public: - H100Spindle(const char* name) : VFD(name) {} - }; -} diff --git a/FluidNC/src/Spindles/H2ASpindle.cpp b/FluidNC/src/Spindles/H2ASpindle.cpp deleted file mode 100644 index 460bf4713..000000000 --- a/FluidNC/src/Spindles/H2ASpindle.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2020 - Stefan de Bruijn -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -/* - H2ASpindle.cpp - - This is for the new H2A H2A VFD based spindle via RS485 Modbus. - - WARNING!!!! - VFDs are very dangerous. They have high voltages and are very powerful - Remove power before changing bits. - - The documentation is okay once you get how it works, but unfortunately - incomplete... See H2ASpindle.md for the remainder of the docs that I - managed to piece together. -*/ - -#include "H2ASpindle.h" - -namespace Spindles { - void H2A::direction_command(SpindleState mode, ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 6; - - data.msg[1] = 0x06; // WRITE - data.msg[2] = 0x20; // Command ID 0x2000 - data.msg[3] = 0x00; - data.msg[4] = 0x00; - data.msg[5] = (mode == SpindleState::Ccw) ? 0x02 : (mode == SpindleState::Cw ? 0x01 : 0x06); - } - - void H2A::set_speed_command(uint32_t dev_speed, ModbusCommand& data) { - // NOTE: H2A inverters are a-symmetrical. You set the speed in 1/100 - // percentages, and you get the speed in RPM. So, we need to convert - // the RPM using maxRPM to a percentage. See MD document for details. - // - // For the H2A VFD, the speed is read directly units of RPM, unlike many - // other VFDs where it is given in Hz times some scale factor. - data.tx_length = 6; - data.rx_length = 6; - - uint16_t speed = (uint32_t(dev_speed) * 10000L) / uint32_t(_maxRPM); - if (speed < 0) { - speed = 0; - } - if (speed > 10000) { - speed = 10000; - } - - data.msg[1] = 0x06; // WRITE - data.msg[2] = 0x10; // Command ID 0x1000 - data.msg[3] = 0x00; - data.msg[4] = speed >> 8; - data.msg[5] = speed & 0xFF; - } - - VFD::response_parser H2A::initialization_sequence(int index, ModbusCommand& data) { - if (index == -1) { - data.tx_length = 6; - data.rx_length = 8; - - // Send: 01 03 B005 0002 - data.msg[1] = 0x03; // READ - data.msg[2] = 0xB0; // B0.05 = Get RPM - data.msg[3] = 0x05; - data.msg[4] = 0x00; // Read 2 values - data.msg[5] = 0x02; - - // Recv: 01 03 00 04 5D C0 03 F6 - // -- -- = 24000 (val #1) - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t maxRPM = (uint16_t(response[4]) << 8) | uint16_t(response[5]); - - if (vfd->_speeds.size() == 0) { - vfd->shelfSpeeds(maxRPM / 4, maxRPM); - } - - vfd->setupSpeeds(maxRPM); // The speed is given directly in RPM - vfd->_slop = 300; // 300 RPM - - static_cast(vfd)->_maxRPM = uint32_t(maxRPM); - - log_info("H2A spindle initialized at " << maxRPM << " RPM"); - - return true; - }; - } else { - return nullptr; - } - } - - VFD::response_parser H2A::get_current_speed(ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 8; - - // Send: 01 03 700C 0002 - data.msg[1] = 0x03; // READ - data.msg[2] = 0x70; // B0.05 = Get speed - data.msg[3] = 0x0C; - data.msg[4] = 0x00; // Read 2 values - data.msg[5] = 0x02; - - // Recv: 01 03 0004 095D 0000 - // ---- = 2397 (val #1) - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - vfd->_sync_dev_speed = (uint16_t(response[4]) << 8) | uint16_t(response[5]); - return true; - }; - } - - VFD::response_parser H2A::get_current_direction(ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 6; - - // Send: 01 03 30 00 00 01 - data.msg[1] = 0x03; // READ - data.msg[2] = 0x30; // Command group ID - data.msg[3] = 0x00; - data.msg[4] = 0x00; // Message ID - data.msg[5] = 0x01; - - // Receive: 01 03 00 02 00 02 - // ----- status - - // TODO: What are we going to do with this? Update vfd state? - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { return true; }; - } - - // Configuration registration - namespace { - SpindleFactory::InstanceBuilder registration("H2A"); - } -} diff --git a/FluidNC/src/Spindles/H2ASpindle.h b/FluidNC/src/Spindles/H2ASpindle.h deleted file mode 100644 index 4b2d65f16..000000000 --- a/FluidNC/src/Spindles/H2ASpindle.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2020 - Stefan de Bruijn -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#pragma once - -#include "VFDSpindle.h" - -namespace Spindles { - class H2A : public VFD { - protected: - void direction_command(SpindleState mode, ModbusCommand& data) override; - void set_speed_command(uint32_t dev_speed, ModbusCommand& data) override; - - response_parser initialization_sequence(int index, ModbusCommand& data) override; - response_parser get_current_speed(ModbusCommand& data) override; - response_parser get_current_direction(ModbusCommand& data) override; - response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } - - bool use_delay_settings() const override { return false; } - bool safety_polling() const override { return false; } - - uint32_t _maxRPM; - - public: - H2A(const char* name) : VFD(name) {} - }; -} diff --git a/FluidNC/src/Spindles/HuanyangSpindle.cpp b/FluidNC/src/Spindles/HuanyangSpindle.cpp deleted file mode 100644 index c2b1bd789..000000000 --- a/FluidNC/src/Spindles/HuanyangSpindle.cpp +++ /dev/null @@ -1,395 +0,0 @@ -// Copyright (c) 2020 - Bart Dring -// Copyright (c) 2020 - Stefan de Bruijn -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -/* - HuanyangSpindle.cpp - - This is for a Huanyang VFD based spindle via RS485 Modbus. - Sorry for the lengthy comments, but finding the details on this - VFD was a PITA. I am just trying to help the next person. - - WARNING!!!! - VFDs are very dangerous. They have high voltages and are very powerful - Remove power before changing bits. - - ============================================================================== - - If a user changes state or RPM level, the command to do that is sent. If - the command is not responded to a message is sent to serial that there was - a timeout. If the system is in a critical state, an alarm will be generated and - the machine stopped. - - If there are no commands to execute, various status items will be polled. If there - is no response, it will behave as described above. It will stop any running jobs with - an alarm. - - =============================================================================== - - Protocol Details - - A lot of good information about the details of all these parameters and how they should - be setup can be found on this page: - https://community.carbide3d.com/t/vfd-parameters-huanyang-model/15459/7 . - - Before using spindle, VFD must be setup for RS485 and match your spindle: - - PD004 400 Base frequency as rated on my spindle (default was 50) - PD005 400 Maximum frequency Hz (Typical for spindles) - PD011 120 Min Speed (Recommend Aircooled=120 Water=100) - PD014 10 Acceleration time (Test to optimize) - PD015 10 Deceleration time (Test to optimize) - PD023 1 Reverse run enabled - PD141 220 Spindle max rated voltage - PD142 3.7 Max current Amps (0.8kw=3.7 1.5kw=7.0, 2.2kw=??) - PD143 2 Poles most are 2 (I think this is only used for RPM calc from Hz) - PD144 3000 Max rated motor revolution at 50 Hz => 24000@400Hz = 3000@50HZ - - PD001 2 RS485 Control of run commands - PD002 2 RS485 Control of operating frequency - PD163 1 RS485 Address: 1 (Typical. OK to change...see below) - PD164 1 RS485 Baud rate: 9600 (Typical. OK to change...see below) - PD165 3 RS485 Mode: RTU, 8N1 - - The official documentation of the RS485 is horrible. I had to piece it together from - a lot of different sources: - - Manuals: https://github.com/RobertOlechowski/Huanyang_VFD/tree/master/Documentations/pdf - Reference: https://github.com/Smoothieware/Smoothieware/blob/edge/src/modules/tools/spindle/HuanyangSpindleControl.cpp - Refernece: https://gist.github.com/Bouni/803492ed0aab3f944066 - VFD settings: https://www.hobbytronics.co.za/Content/external/1159/Spindle_Settings.pdf - Spindle Talker 2 https://github.com/GilchristT/SpindleTalker2/releases - Python https://github.com/RobertOlechowski/Huanyang_VFD - - ========================================================================= - - Commands - ADDR CMD LEN DATA CRC - 0x01 0x03 0x01 0x01 0x31 0x88 Start spindle clockwise - 0x01 0x03 0x01 0x08 0xF1 0x8E Stop spindle - 0x01 0x03 0x01 0x11 0x30 0x44 Start spindle counter-clockwise - - Return values are - 0 = run - 1 = jog - 2 = r/f - 3 = running - 4 = jogging - 5 = r/f - 6 = Braking - 7 = Track start - - ========================================================================== - - Setting RPM - ADDR CMD LEN DATA CRC - 0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ) - - Response is same as data sent - - ========================================================================== - - Setting registers - Addr Read Len Reg DataH DataL CRC CRC - 0x01 0x01 0x03 5 0x00 0x00 CRC CRC // PD005 - 0x01 0x01 0x03 11 0x00 0x00 CRC CRC // PD011 - 0x01 0x01 0x03 143 0x00 0x00 CRC CRC // PD143 - 0x01 0x01 0x03 144 0x00 0x00 CRC CRC // PD144 - - Message is returned with requested value = (DataH * 16) + DataL (see decimal offset above) - - ========================================================================== - - Status registers - Addr Read Len Reg DataH DataL CRC CRC - 0x01 0x04 0x03 0x00 0x00 0x00 CRC CRC // Set Frequency * 100 (25Hz = 2500) - 0x01 0x04 0x03 0x01 0x00 0x00 CRC CRC // Ouput Frequency * 100 - 0x01 0x04 0x03 0x02 0x00 0x00 CRC CRC // Ouput Amps * 10 - 0x01 0x04 0x03 0x03 0x00 0x00 0xF0 0x4E // Read RPM (example CRC shown) - 0x01 0x04 0x03 0x0 0x00 0x00 CRC CRC // DC voltage - 0x01 0x04 0x03 0x05 0x00 0x00 CRC CRC // AC voltage - 0x01 0x04 0x03 0x06 0x00 0x00 CRC CRC // Cont - 0x01 0x04 0x03 0x07 0x00 0x00 CRC CRC // VFD Temp - - Message is returned with requested value = (DataH * 16) + DataL (see decimal offset above) - - ========================================================================== - - The math: - - PD005 400 Maximum frequency Hz (Typical for spindles) - PD011 120 Min Speed (Recommend Aircooled=120 Water=100) - PD143 2 Poles most are 2 (I think this is only used for RPM calc from Hz) - PD144 3000 Max rated motor revolution at 50 Hz => 24000@400Hz = 3000@50HZ - - During initialization these 4 are pulled from the VFD registers. It then sets min and max RPM - of the spindle. So: - - MinRPM = PD011 * PD144 / 50 = 120 * 3000 / 50 = 7200 RPM min - MaxRPM = PD005 * PD144 / 50 = 400 * 3000 / 50 = 24000 RPM max - - If you then set 12000 RPM, it calculates the frequency: - - int targetFrequency = targetRPM * PD005 / MaxRPM = targetRPM * PD005 / (PD005 * PD144 / 50) = - targetRPM * 50 / PD144 = 12000 * 50 / 3000 = 200 - - If the frequency is -say- 25 Hz, Huanyang wants us to send 2500 (eg. 25.00 Hz). -*/ - -#include "HuanyangSpindle.h" - -#include // std::max - -namespace Spindles { - // Baud rate is set in the PD164 setting. If it is not 9600, add, for example, - // _baudrate = 19200; - - void Huanyang::direction_command(SpindleState mode, ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 4; - data.rx_length = 4; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x03; - data.msg[2] = 0x01; - - switch (mode) { - case SpindleState::Cw: - data.msg[3] = 0x01; - break; - case SpindleState::Ccw: - data.msg[3] = 0x11; - break; - default: // SpindleState::Disable - data.msg[3] = 0x08; - break; - } - } - - void IRAM_ATTR Huanyang::set_speed_command(uint32_t dev_speed, ModbusCommand& data) { - // The units for setting Huanyang speed are Hz * 100. For a 2-pole motor, - // RPM is Hz * 60 sec/min. The maximum possible speed is 400 Hz so - // 400 * 60 = 24000 RPM. - - if (dev_speed != 0 && (dev_speed < _minFrequency || dev_speed > _maxFrequency)) { - log_warn(name() << " requested freq " << (dev_speed) << " is outside of range (" << _minFrequency << "," << _maxFrequency << ")"); - } - - // There is a configuration register PD144 that scales the display to show the - // RPMs. It is nominally set to 3000 (= 50Hz * 60) for a 2-pole motor or - // 1500 for a 4-pole motor, but it can be set slightly lower to account for - // slip - the torque-limited difference between actual spindle speed and the - // the frequency delivered to the motor windings. - - // Frequency comes from a conversion of revolutions per second to revolutions per minute - // (factor of 60) and a factor of 2 from counting the number of poles. E.g. rpm * 120 / 100. - - // int targetFrequency = targetRPM * PD005 / MaxRPM - // = targetRPM * PD005 / (PD005 * PD144 / 50) - // = targetRPM * 50 / PD144 - // - // Huanyang wants a factor 100 bigger numbers. So, 1500 rpm -> 25 HZ. Send 2500. - - // The conversion from RPM as requested by the GCode program and the speed number - // (nominally Hz * 100) is done in mapping code that is shared across all spindles. - // The dev_speed argument is precomputed by that code. - - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 5; - data.rx_length = 5; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x05; // Set register command - data.msg[2] = 0x02; // Register PD002 - main frequency in units of 0.01 Hz - data.msg[3] = dev_speed >> 8; - data.msg[4] = dev_speed & 0xFF; - } - - // This gets data from the VFS. It does not set any values - VFD::response_parser Huanyang::initialization_sequence(int index, ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 6; - data.rx_length = 6; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x01; // Read setting - data.msg[2] = 0x03; // Len - // [3] = set below... - data.msg[4] = 0x00; - data.msg[5] = 0x00; - - switch (index) { - case -1: - data.msg[3] = 5; // PD005: max frequency the VFD will allow. Normally 400. - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t value = (response[4] << 8) | response[5]; - - // Set current RPM value? Somewhere? - auto huanyang = static_cast(vfd); - huanyang->_maxFrequency = value; - return true; - }; - break; - case -2: - data.msg[3] = 11; // PD011: frequency lower limit. Normally 0. - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t value = (response[4] << 8) | response[5]; - - // Set current RPM value? Somewhere? - auto huanyang = static_cast(vfd); - huanyang->_minFrequency = value; - - log_info(huanyang->name() << " PD0011, PD005 Freq range (" << (huanyang->_minFrequency / 100) << "," - << (huanyang->_maxFrequency / 100) << ") Hz" - << " (" << (huanyang->_minFrequency / 100 * 60) << "," << (huanyang->_maxFrequency / 100 * 60) - << ") RPM"); - - return true; - }; - break; - case -3: - data.msg[3] = 144; // PD144: max rated motor revolution at 50Hz => 24000@400Hz = 3000@50HZ - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t value = (response[4] << 8) | response[5]; - - // Set current RPM value? Somewhere? - auto huanyang = static_cast(vfd); - huanyang->_maxRpmAt50Hz = value; - - log_info(huanyang->name() << " PD144 Rated RPM @ 50Hz:" << huanyang->_maxRpmAt50Hz); - - // Regarding PD144, the 2 versions of the manuals both say "This is set according to the - // actual revolution of the motor. The displayed value is the same as this set value. It - // can be used as a monitoring parameter, which is convenient to the user. This set value - // corresponds to the revolution at 50Hz". - - // Calculate the VFD settings: - huanyang->updateRPM(); - - return true; - }; - break; - case -4: - data.rx_length = 5; - data.msg[3] = 143; // PD143: 4 or 2 poles in motor. Default is 4. A spindle being 24000RPM@400Hz implies 2 poles - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint8_t value = response[4]; // Single byte response. - auto huanyang = static_cast(vfd); - // Sanity check. We expect something like 2 or 4 poles. - if (value <= 4 && value >= 2) { - // Set current RPM value? Somewhere? - - huanyang->_numberPoles = value; - - log_info(huanyang->name() << " PD143 Poles:" << huanyang->_numberPoles); - - huanyang->updateRPM(); - - return true; - } else { - log_error(huanyang->name() << " PD143 Poles: expected 2-4, got:" << value); - return false; - } - }; - break; - case -5: - data.msg[3] = 14; // Accel value displayed is X.X - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t value = (response[4] << 8) | response[5]; - - auto huanyang = static_cast(vfd); - log_info(huanyang->name() << " PD014 Accel:" << float(value) / 10.0); - return true; - }; - break; - case -6: - data.msg[3] = 15; // Decel alue displayed is X.X - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t value = (response[4] << 8) | response[5]; - - auto huanyang = static_cast(vfd); - log_info(huanyang->name() << " PD015 Decel:" << float(value) / 10.0); - return true; - }; - break; - default: - break; - } - - // Done. - return nullptr; - } - - void Huanyang::updateRPM() { - /* - PD005 = 400.00 ; max frequency the VFD will allow - MaxRPM = PD005 * 60; but see PD176 - - Frequencies are expressed in centiHz. - */ - - if (_minFrequency > _maxFrequency) { - _minFrequency = _maxFrequency; - } - if (_speeds.size() == 0) { - // Convert from Frequency in centiHz (the divisor of 100) to RPM (the factor of 60) - SpindleSpeed minRPM = _minFrequency * 60 / 100; - SpindleSpeed maxRPM = _maxFrequency * 60 / 100; - shelfSpeeds(minRPM, maxRPM); - } - setupSpeeds(_maxFrequency); - _slop = std::max(_maxFrequency / 40, 1); - } - - VFD::response_parser Huanyang::get_status_ok(ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 6; - data.rx_length = 6; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x04; - data.msg[2] = 0x03; - data.msg[3] = reg; - data.msg[4] = 0x00; - data.msg[5] = 0x00; - - if (reg < 0x03) { - reg++; - } else { - reg = 0x00; - } - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { return true; }; - } - - VFD::response_parser Huanyang::get_current_speed(ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 6; - data.rx_length = 6; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x04; - data.msg[2] = 0x03; - data.msg[3] = 0x01; // Output frequency - data.msg[4] = 0x00; - data.msg[5] = 0x00; - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t frequency = (response[4] << 8) | response[5]; - - // Store speed for synchronization - vfd->_sync_dev_speed = frequency; - return true; - }; - } - - // Configuration registration - namespace { - SpindleFactory::InstanceBuilder registration("Huanyang"); - } -} diff --git a/FluidNC/src/Spindles/HuanyangSpindle.h b/FluidNC/src/Spindles/HuanyangSpindle.h deleted file mode 100644 index 57881219a..000000000 --- a/FluidNC/src/Spindles/HuanyangSpindle.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2020 - Bart Dring -// Copyright (c) 2020 - Stefan de Bruijn -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#pragma once - -#include "VFDSpindle.h" - -namespace Spindles { - class Huanyang : public VFD { - private: - int reg; - - protected: - uint16_t _minFrequency = 0; // PD011: frequency lower limit. Normally 0. - uint16_t _maxFrequency = 400; // PD005: max frequency the VFD will allow. Normally 400. - uint16_t _maxRpmAt50Hz = 100; // PD144: rated motor revolution at 50Hz => 24000@400Hz = 3000@50HZ - uint16_t _numberPoles = 2; // PD143: 4 or 2 poles in motor. Default is 4. A spindle being 24000RPM@400Hz implies 2 poles - - void updateRPM(); - - void direction_command(SpindleState mode, ModbusCommand& data) override; - void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - - response_parser initialization_sequence(int index, ModbusCommand& data) override; - response_parser get_status_ok(ModbusCommand& data) override; - response_parser get_current_speed(ModbusCommand& data) override; - - public: - Huanyang(const char* name) : VFD(name) {} - }; -} diff --git a/FluidNC/src/Spindles/NowForeverSpindle.cpp b/FluidNC/src/Spindles/NowForeverSpindle.cpp deleted file mode 100644 index 3cb6c487e..000000000 --- a/FluidNC/src/Spindles/NowForeverSpindle.cpp +++ /dev/null @@ -1,263 +0,0 @@ -#include "NowForeverSpindle.h" - -namespace Spindles { - void NowForever::direction_command(SpindleState mode, ModbusCommand& data) { - data.tx_length = 9; - data.rx_length = 6; - - data.msg[1] = 0x10; // WRITE - data.msg[2] = 0x09; // Register address, high byte (spindle status) - data.msg[3] = 0x00; // Register address, low byte (spindle status) - data.msg[4] = 0x00; // Number of elements, high byte - data.msg[5] = 0x01; // Number of elements, low byte (1 element) - data.msg[6] = 0x02; // Length of first element in bytes (1 regsiter with 2 bytes length) - data.msg[7] = 0x00; // Data, high byte - - /* - Contents of register 0x0900 - Bit 0: run, 1=run, 0=stop - Bit 1: direction, 1=ccw, 0=cw - Bit 2: jog, 1=jog, 0=stop - Bit 3: reset, 1=reset, 0=dont reset - Bit 4-15: reserved - */ - - switch (mode) { - case SpindleState::Cw: - data.msg[8] = 0b00000001; // Data, low byte (run, forward) - log_debug("VFD: Set direction CW"); - break; - - case SpindleState::Ccw: - data.msg[8] = 0b00000011; // Data, low byte (run, reverse) - log_debug("VFD: Set direction CCW"); - break; - - case SpindleState::Disable: - data.msg[8] = 0b00000000; // Data, low byte (run, reverse) - log_debug("VFD: Disabled spindle"); - break; - - default: - log_debug("VFD: Unknown spindle state"); - break; - } - } - - void NowForever::set_speed_command(uint32_t hz, ModbusCommand& data) { - data.tx_length = 9; - data.rx_length = 6; - - data.msg[1] = 0x10; // WRITE - data.msg[2] = 0x09; // Register address, high byte (speed in hz) - data.msg[3] = 0x01; // Register address, low byte (speed in hz) - data.msg[4] = 0x00; // Number of elements, high byte - data.msg[5] = 0x01; // Number of elements, low byte (1 element) - data.msg[6] = 0x02; // Length of first element in bytes (1 regsiter with 2 bytes length) - - /* - Contents of register 0x0901 - Bit 0-15: speed in hz - */ - - data.msg[7] = hz >> 8; // Data, high byte - data.msg[8] = hz & 0xFF; // Data, low byte - - log_debug("VFD: Set speed: " << hz / 100 << "hz or" << (hz * 60 / 100) << "rpm"); - } - - VFD::response_parser NowForever::initialization_sequence(int index, ModbusCommand& data) { - if (index == -1) { - data.tx_length = 6; - data.rx_length = 7; - - data.msg[1] = 0x03; // READ - data.msg[2] = 0x00; // Register address, high byte (speed in hz) - data.msg[3] = 0x07; // Register address, low byte (speed in hz) - data.msg[4] = 0x00; // Number of elements, high byte - data.msg[5] = 0x02; // Number of elements, low byte (2 elements) - - /* - Contents of register 0x0007 - Bit 0-15: max speed in hz * 100 - - Contents of register 0x0008 - Bit 0-15: min speed in hz * 100 - */ - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - if (response[1] != 0x03) { - return false; - } - - // We expect a result length of 4 bytes - if (response[2] != 4) { - return false; - } - - auto nowForever = static_cast(vfd); - - nowForever->_minFrequency = (uint16_t(response[5]) << 8) | uint16_t(response[6]); - nowForever->_maxFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - - log_debug("VFD: Min frequency: " << nowForever->_minFrequency << "hz Min speed:" << (nowForever->_minFrequency * 60 / 100) - << "rpm"); - log_debug("VFD: Max frequency: " << nowForever->_maxFrequency << "hz Max speed:" << (nowForever->_maxFrequency * 60 / 100) - << "rpm"); - - nowForever->updateRPM(); - - return true; - }; - } - - return nullptr; - } - - void NowForever::updateRPM() { - if (_minFrequency > _maxFrequency) { - uint16_t tmp = _minFrequency; - _minFrequency = _maxFrequency; - _maxFrequency = _minFrequency; - } - - if (_speeds.size() == 0) { - SpindleSpeed minRPM = _minFrequency * 60 / 100; - SpindleSpeed maxRPM = _maxFrequency * 60 / 100; - - shelfSpeeds(minRPM, maxRPM); - } - setupSpeeds(_maxFrequency); - _slop = std::max(_maxFrequency / 400, 1); - } - - VFD::response_parser NowForever::get_current_speed(ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 5; - - data.msg[1] = 0x03; // READ - data.msg[2] = 0x05; // Register address, high byte (current output frequency in hz) - data.msg[3] = 0x02; // Register address, low byte (current output frequency in hz) - data.msg[4] = 0x00; // Number of elements, high byte - data.msg[5] = 0x01; // Number of elements, low byte (1 element) - - /* - Contents of register 0x0502 - Bit 0-15: current output frequency in hz * 100 - */ - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t currentHz = 0; - - if (response[1] != 0x03) { - return false; - } - - // We expect a result length of 2 bytes - if (response[2] != 2) { - return false; - } - - // Conversion from hz to rpm not required ? - vfd->_sync_dev_speed = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - - log_debug("VFD: Current speed: " << vfd->_sync_dev_speed / 100 << "hz or " << (vfd->_sync_dev_speed * 60 / 100) << "rpm"); - - return true; - }; - } - - VFD::response_parser NowForever::get_current_direction(ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 5; - - data.msg[1] = 0x03; // READ - data.msg[2] = 0x05; // Register address, high byte (inverter running state) - data.msg[3] = 0x00; // Register address, low byte (inverter running state) - data.msg[4] = 0x00; // Number of elements, high byte - data.msg[5] = 0x01; // Number of elements, low byte (1 element) - - /* - Contents of register 0x0500 - Bit 0: run, 1=run, 0=stop - Bit 1: direction, 1=ccw, 0=cw - Bit 2: control, 1=local, 0=remote - Bit 3: sight fault, 1=fault, 0=no fault - Bit 4: fault, 1=fault, 0=no fault - Bit 5-15: reserved - */ - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - bool running = false; - bool direction = false; // false = cw, true = ccw - - if (response[1] != 0x03) { - return false; - } - - // We expect a result length of 2 bytes - if (response[2] != 2) { - return false; - } - - running = response[4] & 0b00000001; - direction = (response[4] & 0b00000001) >> 1; - - //TODO: Check what to do with the inform ation we have now. - if (running) { - if (direction) { - log_debug("VFD: Got direction CW"); - } else { - log_debug("VFD: Got direction CCW"); - } - } else { - log_debug("VFD: Got spindle not running"); - } - - return true; - }; - } - - VFD::response_parser NowForever::get_status_ok(ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 5; - - data.msg[1] = 0x03; // READ - data.msg[2] = 0x03; // Register address, high byte (current fault number) - data.msg[3] = 0x00; // Register address, low byte (current fault number) - data.msg[4] = 0x00; // Number of elements, high byte - data.msg[5] = 0x01; // Number of elements, low byte (1 element) - - /* - Contents of register 0x0300 - Bit 0-15: current fault number, 0 = no fault, 1~18 = fault number - */ - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t currentFaultNumber = 0; - - if (response[1] != 0x03) { - return false; - } - - // We expect a result length of 2 bytes - if (response[2] != 2) { - return false; - } - - currentFaultNumber = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - - if (currentFaultNumber != 0) { - log_debug("VFD: Got fault number: " << currentFaultNumber); - return false; - } - - return true; - }; - } - - // Configuration registration - namespace { - SpindleFactory::InstanceBuilder registration("NowForever"); - } -} diff --git a/FluidNC/src/Spindles/NowForeverSpindle.h b/FluidNC/src/Spindles/NowForeverSpindle.h deleted file mode 100644 index 0e3e03f88..000000000 --- a/FluidNC/src/Spindles/NowForeverSpindle.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "VFDSpindle.h" - -namespace Spindles { - class NowForever : public VFD { - protected: - uint16_t _minFrequency = 0; - uint16_t _maxFrequency = 0; - - void updateRPM(); - - void direction_command(SpindleState mode, ModbusCommand& data) override; - void set_speed_command(uint32_t hz, ModbusCommand& data) override; - - response_parser initialization_sequence(int index, ModbusCommand& data) override; - response_parser get_current_speed(ModbusCommand& data) override; - response_parser get_current_direction(ModbusCommand& data) override; - response_parser get_status_ok(ModbusCommand& data) override; - bool safety_polling() const { return true; } - - bool use_delay_settings() const override { return false; } - - public: - NowForever(const char* name) : VFD(name) {} - }; -} diff --git a/FluidNC/src/Spindles/SiemensV20Spindle.h b/FluidNC/src/Spindles/SiemensV20Spindle.h deleted file mode 100644 index 07b62bb74..000000000 --- a/FluidNC/src/Spindles/SiemensV20Spindle.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2020 - Bart Dring -// Copyright (c) 2020 - Stefan de Bruijn -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#pragma once - -#include "VFDSpindle.h" - -namespace Spindles { - class SiemensV20 : public VFD { - protected: - uint16_t _minFrequency = 0; // PD011: frequency lower limit. Normally 0. - uint16_t _maxFrequency = 400; // PD005: max frequency the VFD will allow. Normally 400. - uint16_t _numberPoles = 2; // 4 or 2 poles in motor. Default is 4. A spindle being 24000RPM@400Hz implies 2 poles - uint16_t _NumberPhases = 3; // Typically 3 Phases for standard VFDs - float _FreqScaler = - float(-16384.0) / - _maxFrequency; // SPEED_SCALING Internally it is standardized to 16384. (16384 / 400) With this scaling HSW and HIW are transferred via the Modbus register. - - void direction_command(SpindleState mode, ModbusCommand& data) override; - void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - response_parser initialization_sequence(int index, ModbusCommand& data) override; - response_parser get_current_speed(ModbusCommand& data) override; - response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } - - bool use_delay_settings() const override { return false; } - bool safety_polling() const override { return false; } - - public: - SiemensV20(const char* name) : VFD(name) {} - }; -} diff --git a/FluidNC/src/Spindles/Spindle.h b/FluidNC/src/Spindles/Spindle.h index 4fa35b523..fd906cd9e 100644 --- a/FluidNC/src/Spindles/Spindle.h +++ b/FluidNC/src/Spindles/Spindle.h @@ -34,10 +34,10 @@ namespace Spindles { public: Spindle(const char* name) : _name(name) {} - Spindle(const Spindle&) = delete; - Spindle(Spindle&&) = delete; + Spindle(const Spindle&) = delete; + Spindle(Spindle&&) = delete; Spindle& operator=(const Spindle&) = delete; - Spindle& operator=(Spindle&&) = delete; + Spindle& operator=(Spindle&&) = delete; bool _defaultedSpeeds; uint32_t offSpeed() { return _speeds[0].offset; } @@ -111,6 +111,7 @@ namespace Spindles { protected: uint8_t _current_tool = 0; }; + using SpindleFactory = Configuration::GenericFactory; } extern Spindles::Spindle* spindle; diff --git a/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.cpp b/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.cpp new file mode 100644 index 000000000..376c4969d --- /dev/null +++ b/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.cpp @@ -0,0 +1,167 @@ +// Copyright (c) 2024 - Jan Speckamp, whosmatt +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +/* + This is for a Danfoss VLT 2800 VFD based spindle to be controlled via RS485 Modbus RTU. + FluidNC imposes limitations (methods dont have access to full spindle state), while the Danfoss VFD expects the full state to be set with every command. + As an interim solution, the state of the spindle is cached in cachedSpindleState. + + Modbus setup of the VFD is covered in https://files.danfoss.com/download/Drives/doc_A_1_mg10s122.pdf + General setup of the VFD is covered in https://files.danfoss.com/download/Drives/doc_B_1_MG28E902.pdf +*/ + +#include "DanfossVLT2800Protocol.h" +#include "../../Platform.h" +#include "../VFDSpindle.h" + +#include + +#define READ_COIL 0x01 +#define READ_HR 0x03 + +#define WRITE_SINGLE_COIL 0x05 +#define WRITE_MULTIPLE_COIL 0x0F + +namespace Spindles { + namespace VFD { + void DanfossVLT2800Protocol::set_speed_command(uint32_t dev_speed, VFDProtocol::ModbusCommand& data) { + // Cache received speed + cachedSpindleState.speed = dev_speed; + + // Write speed and direction from cache to VFD + writeVFDState(cachedSpindleState, data); + } + + void DanfossVLT2800Protocol::direction_command(SpindleState mode, VFDProtocol::ModbusCommand& data) { + // Cache received direction + cachedSpindleState.state = mode; + + // Write speed and direction from cache to VFD + writeVFDState(cachedSpindleState, data); + } + + VFDProtocol::response_parser DanfossVLT2800Protocol::get_current_speed(VFDProtocol::ModbusCommand& data) { + data.tx_length = 6; // including automatically set client_id, excluding crc + data.rx_length = 3 + 2; // excluding crc + + // We write a full control word instead of setting individual coils + data.msg[1] = READ_HR; + data.msg[2] = 0x14; + data.msg[3] = 0x3b; // start register + data.msg[4] = 0x00; + data.msg[5] = 0x01; // no of points + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + // const uint8_t slave_addr = response[0] + // const uint8_t function = response[1] + // const uint8_t response_byte_count = response[2] + + uint16_t freq = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + vfd->_sync_dev_speed = freq; + return true; + }; + } + + VFDProtocol::response_parser DanfossVLT2800Protocol::get_status_ok_and_init(VFDProtocol::ModbusCommand& data, bool init) { + data.tx_length = 6; // including automatically set client_id, excluding crc + data.rx_length = 5; // excluding crc + + // Read out current state + data.msg[1] = READ_COIL; + data.msg[2] = 0x00; + data.msg[3] = 0x20; // Coil index 32 + data.msg[4] = 0x00; + data.msg[5] = 0x10; // Read 16 Bits + + if (init) { + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + // The VFD is responsive. Initialize speeds. + + auto df = static_cast(detail); + vfd->setupSpeeds(df->_maxFrequency); + + return true; + }; + } else { + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + SpindleStatus status; + status.statusWord = int16_t(response[3]) | uint16_t(response[4] << 8); // See DanfossSpindle.h for structure + +#ifdef DEBUG_VFD + log_debug("Control ready:" << status.flags.control_ready); + log_debug("Drive ready:" << status.flags.drive_ready); + log_debug("Coasting stop:" << status.flags.warning); + log_debug("Trip status:" << status.flags.trip); + log_debug("Trip lock:" << status.flags.trip_lock); + log_debug("No warning/warning:" << status.flags.warning); + log_debug("Speed == ref:" << status.flags.speed_status); + log_debug("Local operation/serial communication control:" << status.flags.local_control); + log_debug("Outside frequency range:" << status.flags.freq_range_err); + log_debug("Motor running:" << status.flags.motor_running); + log_debug("Not used:" << status.flags.voltage_warn); + log_debug("Voltage warn:" << status.flags.voltage_warn); + log_debug("Current limit:" << status.flags.current_limit); + log_debug("Thermal warn:" << status.flags.thermal_warn); +#endif + log_error("TODO: actually check status bits and output potential errors"); + return true; + }; + } + } + + // The VLT2800 expects speed, direction and enable to be sent together at all times. + // This function uses a combined cached spindle state that includes the speed and sends it to the VFD. + void DanfossVLT2800Protocol::writeVFDState(combinedSpindleState spindle, ModbusCommand& data) { + SpindleControl cword; + cword.flags.coasting_stop = 1; + cword.flags.dc_braking_stop = 1; + cword.flags.quick_stop = 1; + cword.flags.freeze_freq = 1; + cword.flags.jog = 0; + cword.flags.reference_preset = 0; + cword.flags.setup_preset = 0; + cword.flags.output_46 = 0; + cword.flags.relay_01 = 0; + cword.flags.data_valid = 1; + cword.flags.reset = 0; + cword.flags.reverse = 0; + + switch (spindle.state) { + case SpindleState::Cw: + cword.flags.reverse = 0; + cword.flags.start_stop = 1; + break; + case SpindleState::Ccw: + cword.flags.reverse = 1; + cword.flags.start_stop = 1; + break; + case SpindleState::Disable: + cword.flags.start_stop = 0; + break; + default: + break; + } + + // Assemble packet: + data.tx_length = 11; + data.rx_length = 6; + + // We write a full control word instead of setting individual coils + data.msg[1] = WRITE_MULTIPLE_COIL; + data.msg[2] = 0x00; + data.msg[3] = 0x00; // start coil address + data.msg[4] = 0x00; + data.msg[5] = 0x20; // write length + data.msg[6] = 0x04; // payload byte count + + data.msg[7] = cword.controlWord & 0xFF; // MSB + data.msg[8] = cword.controlWord >> 8; // LSB + + data.msg[9] = spindle.speed & 0xFF; + data.msg[10] = spindle.speed >> 8; + } + namespace { + SpindleFactory::DependentInstanceBuilder registration("DanfossVLT2800"); + } + } +} diff --git a/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h b/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h new file mode 100644 index 000000000..cd85d1471 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/DanfossVLT2800Protocol.h @@ -0,0 +1,83 @@ +// Copyright (c) 2024 - Jan Speckamp, whosmatt +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class DanfossVLT2800Protocol : public VFDProtocol { + union SpindleStatus { + struct { + bool control_ready : 1; // bit 00 = 0: "" | Bit = 1: "Control ready" + bool drive_ready : 1; // bit 01 = 0: "" | Bit = 1: "Drive ready" + bool coasting_stop : 1; // bit 02 = 0: "Coasting stop" | Bit = 1: "" + bool trip : 1; // bit 03 = 0: "No Trip" | Bit = 1: "Trip" + bool unused1 : 1; // bit 04 Not used + bool unused2 : 1; // bit 05 Not used + bool trip_lock : 1; // bit 06 = 0: "" | Bit = 1: "Trip lock" + bool warning : 1; // bit 07 = 0: "No warning" | Bit = 1: "Warning" + bool speed_status : 1; // bit 08 = 0: "Speed != ref." | Bit = 1: "Speed = ref." + bool local_control : 1; // bit 09 = 0: "Local control" | Bit = 1: "Ser. communi." + bool freq_range_err : 1; // bit 10 = 0: "Outside frequency range" | Bit = 1: "Frequency limit OK" + bool motor_running : 1; // bit 11 = 0: "" | Bit = 1: "Motor running" + bool unused3 : 1; // bit 12 Not used + bool voltage_warn : 1; // bit 13 = 0: "" | Bit = 1: "Voltage warn." + bool current_limit : 1; // bit 14 = 0: "" | Bit = 1: "Current limit" + bool thermal_warn : 1; // bit 15 = 0: "" | Bit = 1: "Thermal wan." + } flags; + uint16_t statusWord; + }; + + union SpindleControl { + struct { + uint8_t reference_preset : 2; // bit 00 = lsb of 2 bit value for preset reference selection + // bit 01 = msb + bool dc_braking_stop : 1; // bit 02 = 0 causes stop with dc brake + bool coasting_stop : 1; // bit 03 = 0 causes coasting stop + bool quick_stop : 1; // bit 04 = 0 causes quick stop + bool freeze_freq : 1; // bit 05 = 0 causes output frequency to be locked from inputs, stops still apply + bool start_stop : 1; // bit 06 = 1 causes motor start, 0 causes motor stop, standard ramp applies + bool reset : 1; // bit 07 = resets trip condition on change from 0 to 1 + bool jog : 1; // bit 08 = 1 switches to jogging (par. 213) + bool ramp_select : 1; // bit 09 = ramp selection: 0 = ramp 1 (par. 207-208), 1 = ramp 2 (par. 209-210) + bool data_valid : 1; // bit 10 = 0 causes entire control word to be ignored + bool relay_01 : 1; // bit 11 = 1 activates relay 01 + bool output_46 : 1; // bit 12 = 1 activates digital output on terminal 46 + uint8_t setup_preset : 2; // bit 13 = lsb of 2 bit value for setup selection when par. 004 multi setup is enabled + // bit 14 = msb + bool reverse : 1; // bit 15 = 1 causes reversing + } flags; + uint16_t controlWord; + }; + + protected: + // Unlike other VFDs, the VLT seems to take speed as int16_t, mapped to 0-200% of the configured maximum reference. + uint16_t _minFrequency = 0x0; // motor off (0% speed) + uint16_t _maxFrequency = 0x4000; // max speed the VFD will allow. 0x4000 = 100% for VLT2800 + + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t rpm, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data) { return get_status_ok_and_init(data, true); } + response_parser get_current_speed(ModbusCommand& data) override; + response_parser get_current_direction(ModbusCommand& data) override { return nullptr; }; + response_parser get_status_ok(ModbusCommand& data) override { return get_status_ok_and_init(data, false); } + + response_parser get_status_ok_and_init(ModbusCommand& data, bool init); + + bool safety_polling() const override { return true; } + + private: + struct combinedSpindleState { + SpindleState state; + uint32_t speed; + } cachedSpindleState; + + void writeVFDState(combinedSpindleState spindle, ModbusCommand& data); + + void parse_spindle_status(uint16_t statusword, SpindleStatus& status); + }; + } +} diff --git a/FluidNC/src/Spindles/VFD/H100Protocol.cpp b/FluidNC/src/Spindles/VFD/H100Protocol.cpp new file mode 100644 index 000000000..4bd2389bb --- /dev/null +++ b/FluidNC/src/Spindles/VFD/H100Protocol.cpp @@ -0,0 +1,179 @@ +// Copyright (c) 2020 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +/* + H100Spindle.cpp + + This is for a H100 VFD based spindle via RS485 Modbus. + + WARNING!!!! + VFDs are very dangerous. They have high voltages and are very powerful + Remove power before changing bits. +*/ + +#include "H100Protocol.h" +#include "../VFDSpindle.h" + +#include // std::max + +namespace Spindles { + namespace VFD { + void H100Protocol::direction_command(SpindleState mode, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 6; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x05; + data.msg[2] = 0x00; + + switch (mode) { + case SpindleState::Cw: //[01] [05] [00 49] [ff 00] -- forward run + data.msg[3] = 0x49; + data.msg[4] = 0xFF; + data.msg[5] = 0x00; + break; + case SpindleState::Ccw: //[01] [05] [00 4A] [ff 00] -- reverse run + data.msg[3] = 0x4A; + data.msg[4] = 0xFF; + data.msg[5] = 0x00; + break; + default: // SpindleState::Disable [01] [05] [00 4B] [ff 00] -- stop + data.msg[3] = 0x4B; + data.msg[4] = 0xFF; + data.msg[5] = 0x00; + break; + } + } + + void H100Protocol::set_speed_command(uint32_t dev_speed, ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 6; + + if (dev_speed != 0 && (dev_speed < _minFrequency || dev_speed > _maxFrequency)) { + log_warn("H100 requested freq " << (dev_speed) << " is outside of range (" << _minFrequency << "," << _maxFrequency << ")"); + } + +#ifdef DEBUG_VFD + log_debug("Setting VFD dev_speed to " << dev_speed); +#endif + + //[01] [06] [0201] [07D0] Set frequency to [07D0] = 200.0 Hz. (2000 is written!) + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x06; // Set register command + data.msg[2] = 0x02; + data.msg[3] = 0x01; + //data.msg[4] = dev_speed >> 8; - BC 11/24/21 + data.msg[4] = dev_speed >> 8; + data.msg[5] = dev_speed & 0xFF; + } + + // This gets data from the VFD. It does not set any values + VFDProtocol::response_parser H100Protocol::initialization_sequence(int index, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 5; + // Read F011 (min frequency) and F005 (max frequency): + // + // [03] [000B] [0001] gives [03] [02] [xxxx] (with 02 the result byte count). + // [03] [0005] [0001] gives [03] [02] [xxxx] (with 02 the result byte count). + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x03; // Read setting + data.msg[2] = 0x00; + // [3] = set below... + data.msg[4] = 0x00; // length + data.msg[5] = 0x01; + + if (index == -1) { + // Max frequency + data.msg[3] = 0x05; // PD005: max frequency the VFD will allow. Normally 400. + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t value = (response[3] << 8) | response[4]; + +#ifdef DEBUG_VFD + log_debug("VFD: Max frequency = " << value / 10 << "Hz " << value / 10 * 60 << "RPM"); +#endif + log_info("VFD: Max speed:" << (value / 10 * 60) << "rpm"); + + // Set current RPM value? Somewhere? + auto h100 = static_cast(detail); + h100->_maxFrequency = value; + + return true; + }; + + } else if (index == -2) { + // Min Frequency + data.msg[3] = 0x0B; // PD011: frequency lower limit. Normally 0. + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t value = (response[3] << 8) | response[4]; + +#ifdef DEBUG_VFD + log_debug("VFD: Min frequency = " << value / 10 << "Hz " << value / 10 * 60 << "RPM"); +#endif + log_info("VFD: Min speed:" << (value / 10 * 60) << "rpm"); + + // Set current RPM value? Somewhere? + auto h100 = static_cast(detail); + h100->_minFrequency = value; + + h100->updateRPM(vfd); + + return true; + }; + } + + // Done. + return nullptr; + } + + void H100Protocol::updateRPM(VFDSpindle* vfd) { + if (_minFrequency > _maxFrequency) { + _minFrequency = _maxFrequency; + } + + if (vfd->_speeds.size() == 0) { + SpindleSpeed minRPM = _minFrequency * 60 / 10; + SpindleSpeed maxRPM = _maxFrequency * 60 / 10; + + vfd->shelfSpeeds(minRPM, maxRPM); + } + vfd->setupSpeeds(_maxFrequency); + vfd->_slop = std::max(_maxFrequency / 40, 1); + + log_info("VFD: VFD settings read: Freq range(" << _minFrequency << " , " << _maxFrequency << ")]"); + } + + VFDProtocol::response_parser H100Protocol::get_current_speed(ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + // [01] [04] [0000] [0002] -- output frequency + data.tx_length = 6; + data.rx_length = 7; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x04; + data.msg[2] = 0x00; + data.msg[3] = 0x00; // Output frequency + data.msg[4] = 0x00; + data.msg[5] = 0x02; + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + // 01 04 04 [freq 16] [set freq 16] [crc16] + uint16_t frequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + // Store speed for synchronization + vfd->_sync_dev_speed = frequency; + return true; + }; + } + + // Configuration registration + namespace { + SpindleFactory::DependentInstanceBuilder registration("H100"); + } + } +} diff --git a/FluidNC/src/Spindles/VFD/H100Protocol.h b/FluidNC/src/Spindles/VFD/H100Protocol.h new file mode 100644 index 000000000..d6a339d17 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/H100Protocol.h @@ -0,0 +1,30 @@ +// Copyright (c) 2020 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class H100Protocol : public VFDProtocol { + private: + int reg = 0; + + protected: + uint16_t _minFrequency = 0; + uint16_t _maxFrequency = 4000; // H100 works with frequencies scaled by 10. + + void updateRPM(VFDSpindle* vfd); + + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t rpm, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } + response_parser get_current_speed(ModbusCommand& data) override; + + bool use_delay_settings() const override { return false; } + }; + } +} diff --git a/FluidNC/src/Spindles/H100Spindle.md b/FluidNC/src/Spindles/VFD/H100Protocol.md similarity index 100% rename from FluidNC/src/Spindles/H100Spindle.md rename to FluidNC/src/Spindles/VFD/H100Protocol.md diff --git a/FluidNC/src/Spindles/VFD/H2AProtocol.cpp b/FluidNC/src/Spindles/VFD/H2AProtocol.cpp new file mode 100644 index 000000000..3345a957b --- /dev/null +++ b/FluidNC/src/Spindles/VFD/H2AProtocol.cpp @@ -0,0 +1,136 @@ +// Copyright (c) 2020 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +/* + H2ASpindle.cpp + + This is for the new H2A H2A VFD based spindle via RS485 Modbus. + + WARNING!!!! + VFDs are very dangerous. They have high voltages and are very powerful + Remove power before changing bits. + + The documentation is okay once you get how it works, but unfortunately + incomplete... See H2ASpindle.md for the remainder of the docs that I + managed to piece together. +*/ + +#include "H2AProtocol.h" +#include "../VFDSpindle.h" + +namespace Spindles { + namespace VFD { + void H2AProtocol::direction_command(SpindleState mode, ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 6; + + data.msg[1] = 0x06; // WRITE + data.msg[2] = 0x20; // Command ID 0x2000 + data.msg[3] = 0x00; + data.msg[4] = 0x00; + data.msg[5] = (mode == SpindleState::Ccw) ? 0x02 : (mode == SpindleState::Cw ? 0x01 : 0x06); + } + + void H2AProtocol::set_speed_command(uint32_t dev_speed, ModbusCommand& data) { + // NOTE: H2A inverters are a-symmetrical. You set the speed in 1/100 + // percentages, and you get the speed in RPM. So, we need to convert + // the RPM using maxRPM to a percentage. See MD document for details. + // + // For the H2A VFD, the speed is read directly units of RPM, unlike many + // other VFDs where it is given in Hz times some scale factor. + data.tx_length = 6; + data.rx_length = 6; + + uint16_t speed = (uint32_t(dev_speed) * 10000L) / uint32_t(_maxRPM); + if (speed < 0) { + speed = 0; + } + if (speed > 10000) { + speed = 10000; + } + + data.msg[1] = 0x06; // WRITE + data.msg[2] = 0x10; // Command ID 0x1000 + data.msg[3] = 0x00; + data.msg[4] = speed >> 8; + data.msg[5] = speed & 0xFF; + } + + VFDProtocol::response_parser H2AProtocol::initialization_sequence(int index, ModbusCommand& data) { + if (index == -1) { + data.tx_length = 6; + data.rx_length = 8; + + // Send: 01 03 B005 0002 + data.msg[1] = 0x03; // READ + data.msg[2] = 0xB0; // B0.05 = Get RPM + data.msg[3] = 0x05; + data.msg[4] = 0x00; // Read 2 values + data.msg[5] = 0x02; + + // Recv: 01 03 00 04 5D C0 03 F6 + // -- -- = 24000 (val #1) + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t maxRPM = (uint16_t(response[4]) << 8) | uint16_t(response[5]); + + if (vfd->_speeds.size() == 0) { + vfd->shelfSpeeds(maxRPM / 4, maxRPM); + } + + vfd->setupSpeeds(maxRPM); // The speed is given directly in RPM + vfd->_slop = 300; // 300 RPM + + static_cast(detail)->_maxRPM = uint32_t(maxRPM); + + log_info("H2A spindle initialized at " << maxRPM << " RPM"); + + return true; + }; + } else { + return nullptr; + } + } + + VFDProtocol::response_parser H2AProtocol::get_current_speed(ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 8; + + // Send: 01 03 700C 0002 + data.msg[1] = 0x03; // READ + data.msg[2] = 0x70; // B0.05 = Get speed + data.msg[3] = 0x0C; + data.msg[4] = 0x00; // Read 2 values + data.msg[5] = 0x02; + + // Recv: 01 03 0004 095D 0000 + // ---- = 2397 (val #1) + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + vfd->_sync_dev_speed = (uint16_t(response[4]) << 8) | uint16_t(response[5]); + return true; + }; + } + + VFDProtocol::response_parser H2AProtocol::get_current_direction(ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 6; + + // Send: 01 03 30 00 00 01 + data.msg[1] = 0x03; // READ + data.msg[2] = 0x30; // Command group ID + data.msg[3] = 0x00; + data.msg[4] = 0x00; // Message ID + data.msg[5] = 0x01; + + // Receive: 01 03 00 02 00 02 + // ----- status + + // TODO: What are we going to do with this? Update vfd state? + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { return true; }; + } + + // Configuration registration + namespace { + SpindleFactory::DependentInstanceBuilder registration("H2A"); + } + } +} diff --git a/FluidNC/src/Spindles/VFD/H2AProtocol.h b/FluidNC/src/Spindles/VFD/H2AProtocol.h new file mode 100644 index 000000000..921daf3ac --- /dev/null +++ b/FluidNC/src/Spindles/VFD/H2AProtocol.h @@ -0,0 +1,26 @@ +// Copyright (c) 2020 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class H2AProtocol : public VFDProtocol { + protected: + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t dev_speed, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser get_current_speed(ModbusCommand& data) override; + response_parser get_current_direction(ModbusCommand& data) override; + response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } + + bool use_delay_settings() const override { return false; } + bool safety_polling() const override { return false; } + + uint32_t _maxRPM; + }; + } +} diff --git a/FluidNC/src/Spindles/H2ASpindle.md b/FluidNC/src/Spindles/VFD/H2AProtocol.md similarity index 100% rename from FluidNC/src/Spindles/H2ASpindle.md rename to FluidNC/src/Spindles/VFD/H2AProtocol.md diff --git a/FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp b/FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp new file mode 100644 index 000000000..c8e3469a7 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/HuanyangProtocol.cpp @@ -0,0 +1,399 @@ +// Copyright (c) 2020 - Bart Dring +// Copyright (c) 2020 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +/* + HuanyangSpindle.cpp + + This is for a Huanyang VFD based spindle via RS485 Modbus. + Sorry for the lengthy comments, but finding the details on this + VFD was a PITA. I am just trying to help the next person. + + WARNING!!!! + VFDs are very dangerous. They have high voltages and are very powerful + Remove power before changing bits. + + ============================================================================== + + If a user changes state or RPM level, the command to do that is sent. If + the command is not responded to a message is sent to serial that there was + a timeout. If the system is in a critical state, an alarm will be generated and + the machine stopped. + + If there are no commands to execute, various status items will be polled. If there + is no response, it will behave as described above. It will stop any running jobs with + an alarm. + + =============================================================================== + + Protocol Details + + A lot of good information about the details of all these parameters and how they should + be setup can be found on this page: + https://community.carbide3d.com/t/vfd-parameters-huanyang-model/15459/7 . + + Before using spindle, VFD must be setup for RS485 and match your spindle: + + PD004 400 Base frequency as rated on my spindle (default was 50) + PD005 400 Maximum frequency Hz (Typical for spindles) + PD011 120 Min Speed (Recommend Aircooled=120 Water=100) + PD014 10 Acceleration time (Test to optimize) + PD015 10 Deceleration time (Test to optimize) + PD023 1 Reverse run enabled + PD141 220 Spindle max rated voltage + PD142 3.7 Max current Amps (0.8kw=3.7 1.5kw=7.0, 2.2kw=??) + PD143 2 Poles most are 2 (I think this is only used for RPM calc from Hz) + PD144 3000 Max rated motor revolution at 50 Hz => 24000@400Hz = 3000@50HZ + + PD001 2 RS485 Control of run commands + PD002 2 RS485 Control of operating frequency + PD163 1 RS485 Address: 1 (Typical. OK to change...see below) + PD164 1 RS485 Baud rate: 9600 (Typical. OK to change...see below) + PD165 3 RS485 Mode: RTU, 8N1 + + The official documentation of the RS485 is horrible. I had to piece it together from + a lot of different sources: + + Manuals: https://github.com/RobertOlechowski/Huanyang_VFD/tree/master/Documentations/pdf + Reference: https://github.com/Smoothieware/Smoothieware/blob/edge/src/modules/tools/spindle/HuanyangSpindleControl.cpp + Refernece: https://gist.github.com/Bouni/803492ed0aab3f944066 + VFD settings: https://www.hobbytronics.co.za/Content/external/1159/Spindle_Settings.pdf + Spindle Talker 2 https://github.com/GilchristT/SpindleTalker2/releases + Python https://github.com/RobertOlechowski/Huanyang_VFD + + ========================================================================= + + Commands + ADDR CMD LEN DATA CRC + 0x01 0x03 0x01 0x01 0x31 0x88 Start spindle clockwise + 0x01 0x03 0x01 0x08 0xF1 0x8E Stop spindle + 0x01 0x03 0x01 0x11 0x30 0x44 Start spindle counter-clockwise + + Return values are + 0 = run + 1 = jog + 2 = r/f + 3 = running + 4 = jogging + 5 = r/f + 6 = Braking + 7 = Track start + + ========================================================================== + + Setting RPM + ADDR CMD LEN DATA CRC + 0x01 0x05 0x02 0x09 0xC4 0xBF 0x0F Write Frequency (0x9C4 = 2500 = 25.00HZ) + + Response is same as data sent + + ========================================================================== + + Setting registers + Addr Read Len Reg DataH DataL CRC CRC + 0x01 0x01 0x03 5 0x00 0x00 CRC CRC // PD005 + 0x01 0x01 0x03 11 0x00 0x00 CRC CRC // PD011 + 0x01 0x01 0x03 143 0x00 0x00 CRC CRC // PD143 + 0x01 0x01 0x03 144 0x00 0x00 CRC CRC // PD144 + + Message is returned with requested value = (DataH * 16) + DataL (see decimal offset above) + + ========================================================================== + + Status registers + Addr Read Len Reg DataH DataL CRC CRC + 0x01 0x04 0x03 0x00 0x00 0x00 CRC CRC // Set Frequency * 100 (25Hz = 2500) + 0x01 0x04 0x03 0x01 0x00 0x00 CRC CRC // Ouput Frequency * 100 + 0x01 0x04 0x03 0x02 0x00 0x00 CRC CRC // Ouput Amps * 10 + 0x01 0x04 0x03 0x03 0x00 0x00 0xF0 0x4E // Read RPM (example CRC shown) + 0x01 0x04 0x03 0x0 0x00 0x00 CRC CRC // DC voltage + 0x01 0x04 0x03 0x05 0x00 0x00 CRC CRC // AC voltage + 0x01 0x04 0x03 0x06 0x00 0x00 CRC CRC // Cont + 0x01 0x04 0x03 0x07 0x00 0x00 CRC CRC // VFD Temp + + Message is returned with requested value = (DataH * 16) + DataL (see decimal offset above) + + ========================================================================== + + The math: + + PD005 400 Maximum frequency Hz (Typical for spindles) + PD011 120 Min Speed (Recommend Aircooled=120 Water=100) + PD143 2 Poles most are 2 (I think this is only used for RPM calc from Hz) + PD144 3000 Max rated motor revolution at 50 Hz => 24000@400Hz = 3000@50HZ + + During initialization these 4 are pulled from the VFD registers. It then sets min and max RPM + of the spindle. So: + + MinRPM = PD011 * PD144 / 50 = 120 * 3000 / 50 = 7200 RPM min + MaxRPM = PD005 * PD144 / 50 = 400 * 3000 / 50 = 24000 RPM max + + If you then set 12000 RPM, it calculates the frequency: + + int targetFrequency = targetRPM * PD005 / MaxRPM = targetRPM * PD005 / (PD005 * PD144 / 50) = + targetRPM * 50 / PD144 = 12000 * 50 / 3000 = 200 + + If the frequency is -say- 25 Hz, Huanyang wants us to send 2500 (eg. 25.00 Hz). +*/ + +#include "HuanyangProtocol.h" + +#include "../VFDSpindle.h" + +#include // std::max + +namespace Spindles { + namespace VFD { + // Baud rate is set in the PD164 setting. If it is not 9600, add, for example, + // _baudrate = 19200; + + void HuanyangProtocol::direction_command(SpindleState mode, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 4; + data.rx_length = 4; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x03; + data.msg[2] = 0x01; + + switch (mode) { + case SpindleState::Cw: + data.msg[3] = 0x01; + break; + case SpindleState::Ccw: + data.msg[3] = 0x11; + break; + default: // SpindleState::Disable + data.msg[3] = 0x08; + break; + } + } + + void HuanyangProtocol::set_speed_command(uint32_t dev_speed, ModbusCommand& data) { + // The units for setting Huanyang speed are Hz * 100. For a 2-pole motor, + // RPM is Hz * 60 sec/min. The maximum possible speed is 400 Hz so + // 400 * 60 = 24000 RPM. + + if (dev_speed != 0 && (dev_speed < _minFrequency || dev_speed > _maxFrequency)) { + log_warn("Huanyang requested freq " << (dev_speed) << " is outside of range (" << _minFrequency << "," << _maxFrequency + << ")"); + } + + // There is a configuration register PD144 that scales the display to show the + // RPMs. It is nominally set to 3000 (= 50Hz * 60) for a 2-pole motor or + // 1500 for a 4-pole motor, but it can be set slightly lower to account for + // slip - the torque-limited difference between actual spindle speed and the + // the frequency delivered to the motor windings. + + // Frequency comes from a conversion of revolutions per second to revolutions per minute + // (factor of 60) and a factor of 2 from counting the number of poles. E.g. rpm * 120 / 100. + + // int targetFrequency = targetRPM * PD005 / MaxRPM + // = targetRPM * PD005 / (PD005 * PD144 / 50) + // = targetRPM * 50 / PD144 + // + // Huanyang wants a factor 100 bigger numbers. So, 1500 rpm -> 25 HZ. Send 2500. + + // The conversion from RPM as requested by the GCode program and the speed number + // (nominally Hz * 100) is done in mapping code that is shared across all spindles. + // The dev_speed argument is precomputed by that code. + + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 5; + data.rx_length = 5; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x05; // Set register command + data.msg[2] = 0x02; // Register PD002 - main frequency in units of 0.01 Hz + data.msg[3] = dev_speed >> 8; + data.msg[4] = dev_speed & 0xFF; + } + + // This gets data from the VFS. It does not set any values + VFDProtocol::response_parser HuanyangProtocol::initialization_sequence(int index, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 6; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x01; // Read setting + data.msg[2] = 0x03; // Len + // [3] = set below... + data.msg[4] = 0x00; + data.msg[5] = 0x00; + + switch (index) { + case -1: + data.msg[3] = 5; // PD005: max frequency the VFD will allow. Normally 400. + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t value = (response[4] << 8) | response[5]; + + // Set current RPM value? Somewhere? + auto huanyang = static_cast(detail); + huanyang->_maxFrequency = value; + return true; + }; + break; + case -2: + data.msg[3] = 11; // PD011: frequency lower limit. Normally 0. + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t value = (response[4] << 8) | response[5]; + + // Set current RPM value? Somewhere? + auto huanyang = static_cast(detail); + huanyang->_minFrequency = value; + + log_info("Huanyang PD0011, PD005 Freq range (" + << (huanyang->_minFrequency / 100) << "," << (huanyang->_maxFrequency / 100) << ") Hz" + << " (" << (huanyang->_minFrequency / 100 * 60) << "," << (huanyang->_maxFrequency / 100 * 60) << ") RPM"); + + return true; + }; + break; + case -3: + data.msg[3] = 144; // PD144: max rated motor revolution at 50Hz => 24000@400Hz = 3000@50HZ + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t value = (response[4] << 8) | response[5]; + + // Set current RPM value? Somewhere? + auto huanyang = static_cast(detail); + huanyang->_maxRpmAt50Hz = value; + + log_info("Huanyang PD144 Rated RPM @ 50Hz:" << huanyang->_maxRpmAt50Hz); + + // Regarding PD144, the 2 versions of the manuals both say "This is set according to the + // actual revolution of the motor. The displayed value is the same as this set value. It + // can be used as a monitoring parameter, which is convenient to the user. This set value + // corresponds to the revolution at 50Hz". + + // Calculate the VFD settings: + huanyang->updateRPM(vfd); + + return true; + }; + break; + case -4: + data.rx_length = 5; + data.msg[3] = 143; // PD143: 4 or 2 poles in motor. Default is 4. A spindle being 24000RPM@400Hz implies 2 poles + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint8_t value = response[4]; // Single byte response. + auto huanyang = static_cast(detail); + // Sanity check. We expect something like 2 or 4 poles. + if (value <= 4 && value >= 2) { + // Set current RPM value? Somewhere? + + huanyang->_numberPoles = value; + + log_info("Huanyang PD143 Poles:" << huanyang->_numberPoles); + + huanyang->updateRPM(vfd); + + return true; + } else { + log_error("Huanyang PD143 Poles: expected 2-4, got:" << value); + return false; + } + }; + break; + case -5: + data.msg[3] = 14; // Accel value displayed is X.X + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t value = (response[4] << 8) | response[5]; + + auto huanyang = static_cast(detail); + log_info("Huanyang PD014 Accel:" << float(value) / 10.0); + return true; + }; + break; + case -6: + data.msg[3] = 15; // Decel alue displayed is X.X + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t value = (response[4] << 8) | response[5]; + + auto huanyang = static_cast(detail); + log_info("Huanyang PD015 Decel:" << float(value) / 10.0); + return true; + }; + break; + default: + break; + } + + // Done. + return nullptr; + } + + void HuanyangProtocol::updateRPM(VFDSpindle* vfd) { + /* + PD005 = 400.00 ; max frequency the VFD will allow + MaxRPM = PD005 * 60; but see PD176 + + Frequencies are expressed in centiHz. + */ + + if (_minFrequency > _maxFrequency) { + _minFrequency = _maxFrequency; + } + if (vfd->_speeds.size() == 0) { + // Convert from Frequency in centiHz (the divisor of 100) to RPM (the factor of 60) + SpindleSpeed minRPM = _minFrequency * 60 / 100; + SpindleSpeed maxRPM = _maxFrequency * 60 / 100; + vfd->shelfSpeeds(minRPM, maxRPM); + } + vfd->setupSpeeds(_maxFrequency); + vfd->_slop = std::max(_maxFrequency / 40, 1); + } + + VFDProtocol::response_parser HuanyangProtocol::get_status_ok(ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 6; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x04; + data.msg[2] = 0x03; + data.msg[3] = reg; + data.msg[4] = 0x00; + data.msg[5] = 0x00; + + if (reg < 0x03) { + reg++; + } else { + reg = 0x00; + } + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { return true; }; + } + + VFDProtocol::response_parser HuanyangProtocol::get_current_speed(ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 6; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x04; + data.msg[2] = 0x03; + data.msg[3] = 0x01; // Output frequency + data.msg[4] = 0x00; + data.msg[5] = 0x00; + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t frequency = (response[4] << 8) | response[5]; + + // Store speed for synchronization + vfd->_sync_dev_speed = frequency; + return true; + }; + } + + // Configuration registration + namespace { + SpindleFactory::DependentInstanceBuilder registration("Huanyang"); + } + } +} diff --git a/FluidNC/src/Spindles/VFD/HuanyangProtocol.h b/FluidNC/src/Spindles/VFD/HuanyangProtocol.h new file mode 100644 index 000000000..e11696d7c --- /dev/null +++ b/FluidNC/src/Spindles/VFD/HuanyangProtocol.h @@ -0,0 +1,31 @@ +// Copyright (c) 2020 - Bart Dring +// Copyright (c) 2020 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class HuanyangProtocol : public VFDProtocol { + private: + int reg = 0; + + protected: + uint16_t _minFrequency = 0; // PD011: frequency lower limit. Normally 0. + uint16_t _maxFrequency = 400; // PD005: max frequency the VFD will allow. Normally 400. + uint16_t _maxRpmAt50Hz = 100; // PD144: rated motor revolution at 50Hz => 24000@400Hz = 3000@50HZ + uint16_t _numberPoles = 2; // PD143: 4 or 2 poles in motor. Default is 4. A spindle being 24000RPM@400Hz implies 2 poles + + void updateRPM(VFDSpindle* vfd); + + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t rpm, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser get_status_ok(ModbusCommand& data) override; + response_parser get_current_speed(ModbusCommand& data) override; + }; + } +} diff --git a/FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp b/FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp new file mode 100644 index 000000000..b02732dcd --- /dev/null +++ b/FluidNC/src/Spindles/VFD/NowForeverProtocol.cpp @@ -0,0 +1,267 @@ +#include "NowForeverProtocol.h" + +#include "../VFDSpindle.h" + +namespace Spindles { + namespace VFD { + void NowForeverProtocol::direction_command(SpindleState mode, ModbusCommand& data) { + data.tx_length = 9; + data.rx_length = 6; + + data.msg[1] = 0x10; // WRITE + data.msg[2] = 0x09; // Register address, high byte (spindle status) + data.msg[3] = 0x00; // Register address, low byte (spindle status) + data.msg[4] = 0x00; // Number of elements, high byte + data.msg[5] = 0x01; // Number of elements, low byte (1 element) + data.msg[6] = 0x02; // Length of first element in bytes (1 regsiter with 2 bytes length) + data.msg[7] = 0x00; // Data, high byte + + /* + Contents of register 0x0900 + Bit 0: run, 1=run, 0=stop + Bit 1: direction, 1=ccw, 0=cw + Bit 2: jog, 1=jog, 0=stop + Bit 3: reset, 1=reset, 0=dont reset + Bit 4-15: reserved + */ + + switch (mode) { + case SpindleState::Cw: + data.msg[8] = 0b00000001; // Data, low byte (run, forward) + log_debug("VFD: Set direction CW"); + break; + + case SpindleState::Ccw: + data.msg[8] = 0b00000011; // Data, low byte (run, reverse) + log_debug("VFD: Set direction CCW"); + break; + + case SpindleState::Disable: + data.msg[8] = 0b00000000; // Data, low byte (run, reverse) + log_debug("VFD: Disabled spindle"); + break; + + default: + log_debug("VFD: Unknown spindle state"); + break; + } + } + + void NowForeverProtocol::set_speed_command(uint32_t hz, ModbusCommand& data) { + data.tx_length = 9; + data.rx_length = 6; + + data.msg[1] = 0x10; // WRITE + data.msg[2] = 0x09; // Register address, high byte (speed in hz) + data.msg[3] = 0x01; // Register address, low byte (speed in hz) + data.msg[4] = 0x00; // Number of elements, high byte + data.msg[5] = 0x01; // Number of elements, low byte (1 element) + data.msg[6] = 0x02; // Length of first element in bytes (1 regsiter with 2 bytes length) + + /* + Contents of register 0x0901 + Bit 0-15: speed in hz + */ + + data.msg[7] = hz >> 8; // Data, high byte + data.msg[8] = hz & 0xFF; // Data, low byte + + log_debug("VFD: Set speed: " << hz / 100 << "hz or" << (hz * 60 / 100) << "rpm"); + } + + VFDProtocol::response_parser NowForeverProtocol::initialization_sequence(int index, ModbusCommand& data) { + if (index == -1) { + data.tx_length = 6; + data.rx_length = 7; + + data.msg[1] = 0x03; // READ + data.msg[2] = 0x00; // Register address, high byte (speed in hz) + data.msg[3] = 0x07; // Register address, low byte (speed in hz) + data.msg[4] = 0x00; // Number of elements, high byte + data.msg[5] = 0x02; // Number of elements, low byte (2 elements) + + /* + Contents of register 0x0007 + Bit 0-15: max speed in hz * 100 + + Contents of register 0x0008 + Bit 0-15: min speed in hz * 100 + */ + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + if (response[1] != 0x03) { + return false; + } + + // We expect a result length of 4 bytes + if (response[2] != 4) { + return false; + } + + auto nowForever = static_cast(detail); + + nowForever->_minFrequency = (uint16_t(response[5]) << 8) | uint16_t(response[6]); + nowForever->_maxFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + log_debug("VFD: Min frequency: " << nowForever->_minFrequency + << "hz Min speed:" << (nowForever->_minFrequency * 60 / 100) << "rpm"); + log_debug("VFD: Max frequency: " << nowForever->_maxFrequency + << "hz Max speed:" << (nowForever->_maxFrequency * 60 / 100) << "rpm"); + + nowForever->updateRPM(vfd); + + return true; + }; + } + + return nullptr; + } + + void NowForeverProtocol::updateRPM(VFDSpindle* spindle) { + if (_minFrequency > _maxFrequency) { + uint16_t tmp = _minFrequency; + _minFrequency = _maxFrequency; + _maxFrequency = _minFrequency; + } + + if (spindle->_speeds.size() == 0) { + SpindleSpeed minRPM = _minFrequency * 60 / 100; + SpindleSpeed maxRPM = _maxFrequency * 60 / 100; + + spindle->shelfSpeeds(minRPM, maxRPM); + } + spindle->setupSpeeds(_maxFrequency); + spindle->_slop = std::max(_maxFrequency / 400, 1); + } + + VFDProtocol::response_parser NowForeverProtocol::get_current_speed(ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 5; + + data.msg[1] = 0x03; // READ + data.msg[2] = 0x05; // Register address, high byte (current output frequency in hz) + data.msg[3] = 0x02; // Register address, low byte (current output frequency in hz) + data.msg[4] = 0x00; // Number of elements, high byte + data.msg[5] = 0x01; // Number of elements, low byte (1 element) + + /* + Contents of register 0x0502 + Bit 0-15: current output frequency in hz * 100 + */ + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t currentHz = 0; + + if (response[1] != 0x03) { + return false; + } + + // We expect a result length of 2 bytes + if (response[2] != 2) { + return false; + } + + // Conversion from hz to rpm not required ? + vfd->_sync_dev_speed = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + log_debug("VFD: Current speed: " << vfd->_sync_dev_speed / 100 << "hz or " << (vfd->_sync_dev_speed * 60 / 100) << "rpm"); + + return true; + }; + } + + VFDProtocol::response_parser NowForeverProtocol::get_current_direction(ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 5; + + data.msg[1] = 0x03; // READ + data.msg[2] = 0x05; // Register address, high byte (inverter running state) + data.msg[3] = 0x00; // Register address, low byte (inverter running state) + data.msg[4] = 0x00; // Number of elements, high byte + data.msg[5] = 0x01; // Number of elements, low byte (1 element) + + /* + Contents of register 0x0500 + Bit 0: run, 1=run, 0=stop + Bit 1: direction, 1=ccw, 0=cw + Bit 2: control, 1=local, 0=remote + Bit 3: sight fault, 1=fault, 0=no fault + Bit 4: fault, 1=fault, 0=no fault + Bit 5-15: reserved + */ + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + bool running = false; + bool direction = false; // false = cw, true = ccw + + if (response[1] != 0x03) { + return false; + } + + // We expect a result length of 2 bytes + if (response[2] != 2) { + return false; + } + + running = response[4] & 0b00000001; + direction = (response[4] & 0b00000001) >> 1; + + //TODO: Check what to do with the inform ation we have now. + if (running) { + if (direction) { + log_debug("VFD: Got direction CW"); + } else { + log_debug("VFD: Got direction CCW"); + } + } else { + log_debug("VFD: Got spindle not running"); + } + + return true; + }; + } + + VFDProtocol::response_parser NowForeverProtocol::get_status_ok(ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 5; + + data.msg[1] = 0x03; // READ + data.msg[2] = 0x03; // Register address, high byte (current fault number) + data.msg[3] = 0x00; // Register address, low byte (current fault number) + data.msg[4] = 0x00; // Number of elements, high byte + data.msg[5] = 0x01; // Number of elements, low byte (1 element) + + /* + Contents of register 0x0300 + Bit 0-15: current fault number, 0 = no fault, 1~18 = fault number + */ + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t currentFaultNumber = 0; + + if (response[1] != 0x03) { + return false; + } + + // We expect a result length of 2 bytes + if (response[2] != 2) { + return false; + } + + currentFaultNumber = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + if (currentFaultNumber != 0) { + log_debug("VFD: Got fault number: " << currentFaultNumber); + return false; + } + + return true; + }; + } + + // Configuration registration + namespace { + SpindleFactory::DependentInstanceBuilder registration("NowForever"); + } + } +} diff --git a/FluidNC/src/Spindles/VFD/NowForeverProtocol.h b/FluidNC/src/Spindles/VFD/NowForeverProtocol.h new file mode 100644 index 000000000..fcb5763c8 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/NowForeverProtocol.h @@ -0,0 +1,26 @@ +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class NowForeverProtocol : public VFDProtocol { + protected: + uint16_t _minFrequency = 0; + uint16_t _maxFrequency = 0; + + void updateRPM(VFDSpindle* spindle); + + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t hz, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser get_current_speed(ModbusCommand& data) override; + response_parser get_current_direction(ModbusCommand& data) override; + response_parser get_status_ok(ModbusCommand& data) override; + bool safety_polling() const { return true; } + + bool use_delay_settings() const override { return false; } + }; + } +} diff --git a/FluidNC/src/Spindles/SiemensV20Spindle.cpp b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.cpp similarity index 58% rename from FluidNC/src/Spindles/SiemensV20Spindle.cpp rename to FluidNC/src/Spindles/VFD/SiemensV20Protocol.cpp index 3c701078a..2152b4341 100644 --- a/FluidNC/src/Spindles/SiemensV20Spindle.cpp +++ b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.cpp @@ -102,114 +102,134 @@ Take note that the serial interface use EVEN parity! */ -#include "SiemensV20Spindle.h" +#include "SiemensV20Protocol.h" + +#include "../VFDSpindle.h" #include // std::max namespace Spindles { - // Baud rate is set in the PD164 setting. If it is not 9600, add, for example, - // _baudrate = 19200; - - void SiemensV20::direction_command(SpindleState mode, ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 6; - data.rx_length = 6; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x06; - data.msg[2] = 0x00; - data.msg[3] = 0x63; - - switch (mode) { - case SpindleState::Cw: - data.msg[4] = 0x0C; - data.msg[5] = 0x7F; - break; - case SpindleState::Ccw: - data.msg[4] = 0x04; - data.msg[5] = 0x7F; - break; - default: // SpindleState::Disable - data.msg[4] = 0x0C; - data.msg[5] = 0x7E; - break; + namespace VFD { + // Baud rate is set in the PD164 setting. If it is not 9600, add, for example, + // _baudrate = 19200; + + void SiemensV20Protocol::direction_command(SpindleState mode, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 6; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x06; + data.msg[2] = 0x00; + data.msg[3] = 0x63; + + switch (mode) { + case SpindleState::Cw: + data.msg[4] = 0x0C; + data.msg[5] = 0x7F; + break; + case SpindleState::Ccw: + data.msg[4] = 0x04; + data.msg[5] = 0x7F; + break; + default: // SpindleState::Disable + data.msg[4] = 0x0C; + data.msg[5] = 0x7E; + break; + } } - } - void IRAM_ATTR SiemensV20::set_speed_command(uint32_t speed, ModbusCommand& data) { - // The units for setting SiemensV20 speed are Hz * 100. For a 2-pole motor, - // RPM is Hz * 60 sec/min. The maximum possible speed is 400 Hz so - // 400 * 60 = 24000 RPM. + void SiemensV20Protocol::set_speed_command(uint32_t speed, ModbusCommand& data) { + // The units for setting SiemensV20 speed are Hz * 100. For a 2-pole motor, + // RPM is Hz * 60 sec/min. The maximum possible speed is 400 Hz so + // 400 * 60 = 24000 RPM. - log_debug("Setting VFD speed to " << uint32_t(speed)); + log_debug("Setting VFD speed to " << uint32_t(speed)); - if (speed != 0 && (speed < _minFrequency || speed > _maxFrequency)) { - log_warn(name() << " requested freq " << uint32_t(speed) << " is outside of range (" << _minFrequency << "," << _maxFrequency - << ")"); - } - /* + if (speed != 0 && (speed < _minFrequency || speed > _maxFrequency)) { + log_warn("Siemens V20 requested freq " << uint32_t(speed) << " is outside of range (" << _minFrequency << "," + << _maxFrequency << ")"); + } + /* V20 has a scalled input and is standardized to 16384 please note Signed numbers work IE -16384 to 16384 but for this implementation only posivite number are allowed */ - int16_t ScaledFreq = speed * _FreqScaler; - log_debug("Setting VFD Scaled Value " << int16_t(ScaledFreq) << " Byte 1 " << uint8_t(ScaledFreq >> 8) << " Byte 2 " - << uint8_t(ScaledFreq & 0xFF)); - - data.tx_length = 6; - data.rx_length = 6; - - data.msg[1] = 0x06; - data.msg[2] = 0x00; - data.msg[3] = 0x64; - data.msg[4] = ScaledFreq >> 8; - data.msg[5] = ScaledFreq & 0xFF; - } - VFD::response_parser SiemensV20::initialization_sequence(int index, ModbusCommand& data) { - /* - The VFD does not have any noticeable registers to set this information up programmatically - For now - it is user set in the software but is a typical setup - */ - if (_minFrequency > _maxFrequency) { - _minFrequency = _maxFrequency; + int16_t ScaledFreq = speed * _FreqScaler; + log_debug("Setting VFD Scaled Value " << int16_t(ScaledFreq) << " Byte 1 " << uint8_t(ScaledFreq >> 8) << " Byte 2 " + << uint8_t(ScaledFreq & 0xFF)); + + data.tx_length = 6; + data.rx_length = 6; + + data.msg[1] = 0x06; + data.msg[2] = 0x00; + data.msg[3] = 0x64; + data.msg[4] = ScaledFreq >> 8; + data.msg[5] = ScaledFreq & 0xFF; } - if (_speeds.size() == 0) { - //RPM = (Frequency * (360/ Num_Phases))/Num_Poles - SpindleSpeed minRPM = (_minFrequency * (360 / _NumberPhases)) / _numberPoles; - SpindleSpeed maxRPM = (_maxFrequency * (360 / _NumberPhases)) / _numberPoles; - shelfSpeeds(minRPM, maxRPM); + + VFDProtocol::response_parser SiemensV20Protocol::initialization_sequence(int index, ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 5; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x03; + data.msg[2] = 0x00; + data.msg[3] = 0x6E; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + /* + The VFD does not have any noticeable registers to set this information up programmatically + For now - it is user set in the software but is a typical setup + */ + auto siemens = static_cast(detail); + + if (siemens->_minFrequency > siemens->_maxFrequency) { + siemens->_minFrequency = siemens->_maxFrequency; + } + if (vfd->_speeds.size() == 0) { + //RPM = (Frequency * (360/ Num_Phases))/Num_Poles + SpindleSpeed minRPM = (siemens->_minFrequency * (360 / siemens->_NumberPhases)) / siemens->_numberPoles; + SpindleSpeed maxRPM = (siemens->_maxFrequency * (360 / siemens->_NumberPhases)) / siemens->_numberPoles; + vfd->shelfSpeeds(minRPM, maxRPM); + } + vfd->setupSpeeds(siemens->_maxFrequency); + vfd->_slop = std::max(siemens->_maxFrequency / 40, 1); + return true; + }; } - setupSpeeds(_maxFrequency); - _slop = std::max(_maxFrequency / 40, 1); - return nullptr; - } - VFD::response_parser SiemensV20::get_current_speed(ModbusCommand& data) { - // NOTE: data length is excluding the CRC16 checksum. - data.tx_length = 6; - data.rx_length = 5; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x03; - data.msg[2] = 0x00; - data.msg[3] = 0x6E; - data.msg[4] = 0x00; - data.msg[5] = 0x01; - - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - auto siemensV20 = static_cast(vfd); - int16_t Scaledfrequency = ((response[3] << 8) | response[4]); - int16_t frequency = float(Scaledfrequency) / (-1 * (siemensV20->_FreqScaler)); - log_debug("VFD Measured Value " << int16_t(Scaledfrequency) << " Freq " << int16_t(frequency)); - - // Store speed for synchronization - vfd->_sync_dev_speed = uint16_t(frequency); - return true; - }; - } + VFDProtocol::response_parser SiemensV20Protocol::get_current_speed(ModbusCommand& data) { + // NOTE: data length is excluding the CRC16 checksum. + data.tx_length = 6; + data.rx_length = 5; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x03; + data.msg[2] = 0x00; + data.msg[3] = 0x6E; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + auto siemensV20 = static_cast(detail); + int16_t Scaledfrequency = ((response[3] << 8) | response[4]); + int16_t frequency = float(Scaledfrequency) / (-1 * (siemensV20->_FreqScaler)); + log_debug("VFD Measured Value " << int16_t(Scaledfrequency) << " Freq " << int16_t(frequency)); + + // Store speed for synchronization + vfd->_sync_dev_speed = uint16_t(frequency); + return true; + }; + } - // Configuration registration - namespace { - SpindleFactory::InstanceBuilder registration("SiemensV20"); + // Configuration registration + namespace { + SpindleFactory::DependentInstanceBuilder registration("SiemensV20"); + } } } diff --git a/FluidNC/src/Spindles/VFD/SiemensV20Protocol.h b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.h new file mode 100644 index 000000000..b7eabd39f --- /dev/null +++ b/FluidNC/src/Spindles/VFD/SiemensV20Protocol.h @@ -0,0 +1,32 @@ +// Copyright (c) 2020 - Bart Dring +// Copyright (c) 2020 - Stefan de Bruijn +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class SiemensV20Protocol : public VFDProtocol { + protected: + uint16_t _minFrequency = 0; // PD011: frequency lower limit. Normally 0. + uint16_t _maxFrequency = 400; // PD005: max frequency the VFD will allow. Normally 400. + uint16_t _numberPoles = 2; // 4 or 2 poles in motor. Default is 4. A spindle being 24000RPM@400Hz implies 2 poles + uint16_t _NumberPhases = 3; // Typically 3 Phases for standard VFDs + + float _FreqScaler = + float(-16384.0) / + _maxFrequency; // SPEED_SCALING Internally it is standardized to 16384. (16384 / 400) With this scaling HSW and HIW are transferred via the Modbus register. + + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t rpm, ModbusCommand& data) override; + response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser get_current_speed(ModbusCommand& data) override; + response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } + + bool use_delay_settings() const override { return false; } + bool safety_polling() const override { return false; } + }; + } +} diff --git a/FluidNC/src/Spindles/VFD/VFDProtocol.cpp b/FluidNC/src/Spindles/VFD/VFDProtocol.cpp new file mode 100644 index 000000000..84458bb4b --- /dev/null +++ b/FluidNC/src/Spindles/VFD/VFDProtocol.cpp @@ -0,0 +1,291 @@ +#include "VFDProtocol.h" + +#include "../VFDSpindle.h" +#include "../../MotionControl.h" // mc_critical + +#include +#include +#include + +namespace Spindles +{ + namespace VFD + { + const int VFD_RS485_BUF_SIZE = 127; + const int RESPONSE_WAIT_MS = 1000; // how long to wait for a response + const int VFD_RS485_POLL_RATE = 250; // in milliseconds between commands + const TickType_t response_ticks = RESPONSE_WAIT_MS / portTICK_PERIOD_MS; // in milliseconds between commands + + QueueHandle_t VFDProtocol::vfd_cmd_queue = nullptr; + TaskHandle_t VFDProtocol::vfd_cmdTaskHandle = nullptr; + + void VFDProtocol::reportParsingErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length) { + #ifdef DEBUG_VFD + hex_msg(cmd.msg, "RS485 Tx: ", cmd.tx_length); + hex_msg(rx_message, "RS485 Rx: ", read_length); + #endif + } + void VFDProtocol::reportCmdErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length, uint8_t id) { + #ifdef DEBUG_VFD + hex_msg(cmd.msg, "RS485 Tx: ", cmd.tx_length); + hex_msg(rx_message, "RS485 Rx: ", read_length); + + if (read_length != 0) { + if (rx_message[0] != id) { + log_info("RS485 received message from other modbus device"); + } else if (read_length != cmd.rx_length) { + log_info("RS485 received message of unexpected length; expected:" << int(cmd.rx_length) << " got:" << int(read_length)); + } else { + log_info("RS485 CRC check failed"); + } + } else { + log_info("RS485 No response"); + } + #endif + } + + // The communications task + void VFDProtocol::vfd_cmd_task(void* pvParameters) { + static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive + static int pollidx = -1; + + VFDSpindle* instance = static_cast(pvParameters); + auto impl = instance->detail_; + auto& uart = *instance->_uart; + ModbusCommand next_cmd; + uint8_t rx_message[VFD_RS485_MAX_MSG_SIZE]; + bool safetyPollingEnabled = impl->safety_polling(); + + for (; true; delay_ms(VFD_RS485_POLL_RATE)) { + std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // read fence for settings + response_parser parser = nullptr; + + // First check if we should ask the VFD for the speed parameters as part of the initialization. + if (pollidx < 0 && (parser = impl->initialization_sequence(pollidx, next_cmd)) != nullptr) { + } else { + pollidx = 1; // Done with initialization. Main sequence. + } + next_cmd.critical = false; + + VFDaction action; + if (parser == nullptr) { + // If we don't have a parser, the queue goes first. + if (xQueueReceive(vfd_cmd_queue, &action, 0)) { + switch (action.action) { + case actionSetSpeed: + if (!impl->prepareSetSpeedCommand(action.arg, next_cmd, instance)) { + // prepareSetSpeedCommand() can return false if the speed + // change is unnecessary - already at that speed. + // In that case we just discard the command. + continue; // main loop + } + next_cmd.critical = action.critical; + break; + case actionSetMode: + log_debug("vfd_cmd_task mode:" << action.action); + if (!impl->prepareSetModeCommand(SpindleState(action.arg), next_cmd, instance)) { + continue; // main loop + } + next_cmd.critical = action.critical; + break; + } + } else { + // We do not have a parser and there is nothing in the queue, so we cycle + // through the set of periodic queries. + + // We poll in a cycle. Note that the switch will fall through unless we encounter a hit. + // The weakest form here is 'get_status_ok' which should be implemented if the rest fails. + if (instance->_syncing) { + parser = impl->get_current_speed(next_cmd); + } else if (safetyPollingEnabled) { + switch (pollidx) { + case 1: + parser = impl->get_current_speed(next_cmd); + if (parser) { + pollidx = 2; + break; + } + // fall through if get_current_speed did not return a parser + case 2: + parser = impl->get_current_direction(next_cmd); + if (parser) { + pollidx = 3; + break; + } + // fall through if get_current_direction did not return a parser + case 3: + default: + parser = impl->get_status_ok(next_cmd); + pollidx = 1; + + // we could complete this in case parser == nullptr with some ifs, but let's + // just keep it easy and wait an iteration. + break; + } + } + + // If we have no parser, that means get_status_ok is not implemented (and we have + // nothing resting in our queue). Let's fall back on a simple continue. + if (parser == nullptr) { + continue; // main loop + } + } + } + + // At this point next_cmd has been filled with a command block + { + // Fill in the fields that are the same for all protocol variants + next_cmd.msg[0] = instance->_modbus_id; + + // Grabbed the command. Add the CRC16 checksum: + auto crc16 = ModRTU_CRC(next_cmd.msg, next_cmd.tx_length); + next_cmd.msg[next_cmd.tx_length++] = (crc16 & 0xFF); + next_cmd.msg[next_cmd.tx_length++] = (crc16 & 0xFF00) >> 8; + next_cmd.rx_length += 2; + + #ifdef DEBUG_VFD_ALL + if (parser == nullptr) { + hex_msg(next_cmd.msg, "RS485 Tx: ", next_cmd.tx_length); + } + #endif + } + + // Assume for the worst, and retry... + int retry_count = 0; + for (; retry_count < MAX_RETRIES; ++retry_count) { + // Flush the UART and write the data: + uart.flush(); + uart.write(next_cmd.msg, next_cmd.tx_length); + uart.flushTxTimed(response_ticks); + + // Read the response + size_t read_length = 0; + size_t current_read = uart.timedReadBytes(rx_message, next_cmd.rx_length, response_ticks); + read_length += current_read; + + // Apparently some Huanyang report modbus errors in the correct way, and the rest not. Sigh. + // Let's just check for the condition, and truncate the first byte. + if (read_length > 0 && instance->_modbus_id != 0 && rx_message[0] == 0) { + memmove(rx_message + 1, rx_message, read_length - 1); + } + + while (read_length < next_cmd.rx_length && current_read > 0) { + // Try to read more; we're not there yet... + current_read = uart.timedReadBytes(rx_message + read_length, next_cmd.rx_length - read_length, response_ticks); + read_length += current_read; + } + + // Generate crc16 for the response: + auto crc16response = ModRTU_CRC(rx_message, next_cmd.rx_length - 2); + + if (read_length == next_cmd.rx_length && // check expected length + rx_message[0] == instance->_modbus_id && // check address + rx_message[read_length - 1] == (crc16response & 0xFF00) >> 8 && // check CRC byte 1 + rx_message[read_length - 2] == (crc16response & 0xFF)) { // check CRC byte 1 + + // Success + unresponsive = false; + retry_count = MAX_RETRIES + 1; // stop retry'ing + + // Should we parse this? + if (parser != nullptr) { + if (parser(rx_message, instance, impl)) { + // If we're initializing, move to the next initialization command: + if (pollidx < 0) { + --pollidx; + } + } else { + // Parsing failed + reportParsingErrors(next_cmd, rx_message, read_length); + + // If we were initializing, move back to where we started. + unresponsive = true; + pollidx = -1; // Re-initializing the VFD seems like a plan + log_info("Spindle RS485 did not give a satisfying response"); + } + } + } else { + reportCmdErrors(next_cmd, rx_message, read_length, instance->_modbus_id); + + // Wait a bit before we retry. Set the delay to poll-rate. Not sure + // if we should use a different value... + delay_ms(VFD_RS485_POLL_RATE); + + #ifdef DEBUG_TASK_STACK + static UBaseType_t uxHighWaterMark = 0; + reportTaskStackSize(uxHighWaterMark); + #endif + } + } + + if (retry_count == MAX_RETRIES) { + if (!unresponsive) { + log_info("VFD RS485 Unresponsive"); + unresponsive = true; + pollidx = -1; + } + if (next_cmd.critical) { + mc_critical(ExecAlarm::SpindleControl); + log_error("Critical VFD RS485 Unresponsive"); + } + } + } + } + + bool VFDProtocol::prepareSetModeCommand(SpindleState mode, ModbusCommand& data, VFDSpindle* spindle) { + // Do variant-specific command preparation + direction_command(mode, data); + + if (mode == SpindleState::Disable) { + if (!xQueueReset(vfd_cmd_queue)) { + log_info(spindle->name() << " spindle off, queue could not be reset"); + } + } + + spindle->_current_state = mode; + return true; + } + + bool VFDProtocol::prepareSetSpeedCommand(uint32_t speed, ModbusCommand& data, VFDSpindle* spindle) { + log_debug("prep speed " << speed << " curr " << spindle->_current_dev_speed); + if (speed == spindle->_current_dev_speed) { // prevent setting same speed twice + return false; + } + spindle->_current_dev_speed = speed; + + #ifdef DEBUG_VFD_ALL + log_debug("Setting spindle speed to:" << int(speed)); + #endif + // Do variant-specific command preparation + set_speed_command(speed, data); + + // Sometimes sync_dev_speed is retained between different set_speed_command's. We don't want that - we want + // spindle sync to kick in after we set the speed. This forces that. + spindle->_sync_dev_speed = UINT32_MAX; + + return true; + } + + // Calculate the CRC on all of the byte except the last 2 + // It then added the CRC to those last 2 bytes + // full_msg_len This is the length of the message including the 2 crc bytes + // Source: https://ctlsys.com/support/how_to_compute_the_modbus_rtu_message_crc/ + uint16_t VFDProtocol::ModRTU_CRC(uint8_t* buf, int msg_len) { + uint16_t crc = 0xFFFF; + for (int pos = 0; pos < msg_len; pos++) { + crc ^= uint16_t(buf[pos]); // XOR byte into least sig. byte of crc. + + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } else { // Else LSB is not set + crc >>= 1; // Just shift right + } + } + } + + return crc; + } + } +} diff --git a/FluidNC/src/Spindles/VFD/VFDProtocol.h b/FluidNC/src/Spindles/VFD/VFDProtocol.h new file mode 100644 index 000000000..70a14c434 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/VFDProtocol.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include "../Spindle.h" + +namespace Spindles { + class VFDSpindle; + + namespace VFD { + // VFDProtocol resides in a separate class because it doesn't need to be in IRAM. This contains all the + // VFD specific code, which is called from a separate task. + class VFDProtocol { + public: + using response_parser = bool (*)(const uint8_t* response, VFDSpindle* spindle, VFDProtocol* detail); + + static const int VFD_RS485_MAX_MSG_SIZE = 16; // more than enough for a modbus message + static const int MAX_RETRIES = 5; // otherwise the spindle is marked 'unresponsive' + + struct ModbusCommand { + bool critical; // TODO SdB: change into `uint8_t critical : 1;`: We want more flags... + + uint8_t tx_length; + uint8_t rx_length; + uint8_t msg[VFD_RS485_MAX_MSG_SIZE]; + }; + + protected: + // Enable spindown / spinup settings: + virtual bool use_delay_settings() const { return true; } + + // Commands: + virtual void direction_command(SpindleState mode, ModbusCommand& data) = 0; + virtual void set_speed_command(uint32_t rpm, ModbusCommand& data) = 0; + + // Commands that return the status. Returns nullptr if unavailable by this VFD (default): + + virtual response_parser initialization_sequence(int index, ModbusCommand& data) { return nullptr; } + virtual response_parser get_current_speed(ModbusCommand& data) { return nullptr; } + virtual response_parser get_current_direction(ModbusCommand& data) { return nullptr; } + virtual response_parser get_status_ok(ModbusCommand& data) = 0; + virtual bool safety_polling() const { return true; } + + private: + friend class Spindles::VFDSpindle; // For ISR related things. + + enum VFDactionType : uint8_t { actionSetSpeed, actionSetMode }; + + struct VFDaction { + VFDactionType action; + bool critical; + uint32_t arg; + }; + + // Careful observers will notice that these *shouldn't* be static, but they are. The reason is + // hard to track down. In the spindle class, you can find: + // + // 'virtual void init() = 0; // not in constructor because this also gets called when $$ settings change' + // + // With init being called multiple times, static suddenly makes more sense - especially since there is + // no de-init. Oh well... + + static QueueHandle_t vfd_cmd_queue; + static TaskHandle_t vfd_cmdTaskHandle; + static void vfd_cmd_task(void* pvParameters); + + static uint16_t ModRTU_CRC(uint8_t* buf, int msg_len); + bool prepareSetModeCommand(SpindleState mode, ModbusCommand& data, VFDSpindle* spindle); + bool prepareSetSpeedCommand(uint32_t speed, ModbusCommand& data, VFDSpindle* spindle); + + static void reportParsingErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length); + static void reportCmdErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length, uint8_t id); + + public: + VFDProtocol() {} + VFDProtocol(const VFDProtocol&) = delete; + VFDProtocol(VFDProtocol&&) = delete; + VFDProtocol& operator=(const VFDProtocol&) = delete; + VFDProtocol& operator=(VFDProtocol&&) = delete; + }; + } +} diff --git a/FluidNC/src/Spindles/VFD/YL620Protocol.cpp b/FluidNC/src/Spindles/VFD/YL620Protocol.cpp new file mode 100644 index 000000000..a4643cf5d --- /dev/null +++ b/FluidNC/src/Spindles/VFD/YL620Protocol.cpp @@ -0,0 +1,231 @@ +// Copyright (c) 2021 - Marco Wagner +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +/* + This is for a Yalang YL620/YL620-A VFD based spindle to be controlled via RS485 Modbus RTU. + + WARNING!!!! + VFDs are very dangerous. They have high voltages and are very powerful + Remove power before changing bits. + + ============================================================================================================= + A Chinese manual for Modbus communication to YL620 can be found at + https://docs.google.com/document/d/1TkERAvHZby4uad_i9kSk19HlDhbM7_xd/edit + You can use Google Translate to translate it. + + Only Modbus RTU mode is supported, not Modbus ASCII mode. + + Manual Configuration required for the YL620 + + Parameter number Description Value + ------------------------------------------------------------------------------- + P00.00 Main frequency 400.00Hz (match to your spindle) + P00.01 Command source 3 + + P03.00 RS485 Baud rate 3 (9600) + P03.01 RS485 address 1 + P03.02 RS485 protocol 2 + P03.08 Frequency given lower limit 100.0Hz (match to your spindle cooling-type) + + =============================================================================================================== + + RS485 communication is standard Modbus RTU + + Therefore, the following operation codes are relevant: + 0x03: read single holding register + 0x06: write single holding register + + Given a parameter Pnn.mm, the high byte of the register address is nn, + the low is mm. The numbers nn and mm in the manual are given in decimal, + so P13.16 would be register address 0x0d10 when represented in hex. + + Holding register address Description + --------------------------------------------------------------------------- + 0x0000 main frequency + 0x0308 frequency given lower limit + + 0x2000 command register (further information below) + 0x2001 Modbus485 frequency command (x0.1Hz => 2500 = 250.0Hz) + + 0x200A Target frequency + 0x200B Output frequency + 0x200C Output current + + + Command register at holding address 0x2000 + -------------------------------------------------------------------------- + bit 1:0 b00: No function + b01: shutdown command + b10: start command + b11: Jog command + bit 3:2 reserved + bit 5:4 b00: No function + b01: Forward command + b10: Reverse command + b11: change direction + bit 7:6 b00: No function + b01: reset an error flag + b10: reset all error flags + b11: reserved +*/ + +#include "YL620Protocol.h" + +#include "../VFDSpindle.h" + +#include + +namespace Spindles { + namespace VFD { + void YL620Protocol::direction_command(SpindleState mode, ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 6; + + // data.msg[0] is omitted (modbus address is filled in later) + data.msg[1] = 0x06; // 06: write output register + data.msg[2] = 0x20; // 0x2000: command register address + data.msg[3] = 0x00; + + data.msg[4] = 0x00; // High-Byte of command always 0x00 + switch (mode) { + case SpindleState::Cw: + data.msg[5] = 0x12; // Start in forward direction + break; + case SpindleState::Ccw: + data.msg[5] = 0x22; // Start in reverse direction + break; + default: // SpindleState::Disable + data.msg[5] = 0x01; // Disable spindle + break; + } + } + + void YL620Protocol::set_speed_command(uint32_t speed, ModbusCommand& data) { +#ifdef DEBUG_VFD + log_debug("Setting VFD speed to " << speed); +#endif + + data.tx_length = 6; + data.rx_length = 6; + + data.msg[1] = 0x06; + data.msg[2] = 0x20; + data.msg[3] = 0x01; + data.msg[4] = speed >> 8; + data.msg[5] = speed & 0xFF; + } + + VFDProtocol::response_parser YL620Protocol::initialization_sequence(int index, ModbusCommand& data) { + if (index == -1) { + data.tx_length = 6; + data.rx_length = 5; + + data.msg[1] = 0x03; + data.msg[2] = 0x03; + data.msg[3] = 0x08; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Recv: 01 03 02 03 E8 xx xx + // -- -- = 1000 + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + auto yl620 = static_cast(detail); + yl620->_minFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + +#ifdef DEBUG_VFD + log_debug("YL620 allows minimum frequency of:" << yl620->_minFrequency << " Hz"); +#endif + + return true; + }; + } else if (index == -2) { + data.tx_length = 6; + data.rx_length = 5; + + data.msg[1] = 0x03; + data.msg[2] = 0x00; + data.msg[3] = 0x00; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Recv: 01 03 02 0F A0 xx xx + // -- -- = 4000 + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + auto yl620 = static_cast(detail); + yl620->_maxFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + // frequency is in Hz * 10, so RPM is frequency * 60 / 10 = frequency * 6 + // E.g. for 400 Hz, we have frequency = 4000, so 4000 * 6 = 24000 RPM + + if (vfd->_speeds.size() == 0) { + // Convert from frequency in deciHz to RPM (*60/10) + SpindleSpeed maxRPM = yl620->_maxFrequency * 6; + SpindleSpeed minRPM = yl620->_minFrequency * 6; + + vfd->shelfSpeeds(minRPM, maxRPM); + } + + vfd->setupSpeeds(yl620->_maxFrequency); + vfd->_slop = std::max(int(yl620->_maxFrequency) / 40, 1); + + // vfd->_min_rpm = uint32_t(vfd->_max_rpm) * uint32_t(yl620->_minFrequency) / + // uint32_t(yl620->_maxFrequency); // 1000 * 24000 / 4000 = 6000 RPM. + +#ifdef DEBUG_VFD + log_debug("YL620 allows maximum frequency " << yl620->_maxFrequency << " Hz"); +#endif + + return true; + }; + } else { + return nullptr; + } + } + + VFDProtocol::response_parser YL620Protocol::get_current_speed(ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 5; + + // Send: 01 03 200B 0001 + data.msg[1] = 0x03; + data.msg[2] = 0x20; + data.msg[3] = 0x0B; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Recv: 01 03 02 05 DC xx xx + // ---- = 1500 + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { + uint16_t freq = (uint16_t(response[3]) << 8) | uint16_t(response[4]); + + auto yl620 = static_cast(detail); + + vfd->_sync_dev_speed = freq; + return true; + }; + } + + VFDProtocol::response_parser YL620Protocol::get_current_direction(ModbusCommand& data) { + data.tx_length = 6; + data.rx_length = 5; + + // Send: 01 03 20 00 00 01 + data.msg[1] = 0x03; + data.msg[2] = 0x20; + data.msg[3] = 0x00; + data.msg[4] = 0x00; + data.msg[5] = 0x01; + + // Receive: 01 03 02 00 0A xx xx + // ----- status is in 00 0A bit 5:4 + + // TODO: What are we going to do with this? Update vfd state? + return [](const uint8_t* response, VFDSpindle* vfd, VFDProtocol* detail) -> bool { return true; }; + } + + // Configuration registration + namespace { + SpindleFactory::DependentInstanceBuilder registration("YL620"); + } + } +} diff --git a/FluidNC/src/Spindles/VFD/YL620Protocol.h b/FluidNC/src/Spindles/VFD/YL620Protocol.h new file mode 100644 index 000000000..ac4686fd8 --- /dev/null +++ b/FluidNC/src/Spindles/VFD/YL620Protocol.h @@ -0,0 +1,26 @@ +// Copyright (c) 2021 - Marco Wagner +// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. + +#pragma once + +#include "VFDProtocol.h" + +namespace Spindles { + namespace VFD { + class YL620Protocol : public VFDProtocol { + protected: + uint16_t _minFrequency = 0; // frequency lower limit. Factor 10 of actual frequency + uint16_t _maxFrequency = 4000; // max frequency the VFD will allow. Normally 400.0. Factor 10 of actual frequency + + void direction_command(SpindleState mode, ModbusCommand& data) override; + void set_speed_command(uint32_t rpm, ModbusCommand& data) override; + + response_parser initialization_sequence(int index, ModbusCommand& data) override; + response_parser get_current_speed(ModbusCommand& data) override; + response_parser get_current_direction(ModbusCommand& data) override; + response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } + + bool safety_polling() const override { return false; } + }; + } +} diff --git a/FluidNC/src/Spindles/VFDSpindle.cpp b/FluidNC/src/Spindles/VFDSpindle.cpp index 21381dd01..31315e225 100644 --- a/FluidNC/src/Spindles/VFDSpindle.cpp +++ b/FluidNC/src/Spindles/VFDSpindle.cpp @@ -19,242 +19,26 @@ */ #include "VFDSpindle.h" +#include "VFD/VFDProtocol.h" -#include "src/Machine/MachineConfig.h" -#include "src/MotionControl.h" // mc_critical -#include "src/Protocol.h" // rtAlarm -#include "src/Report.h" // hex message -#include "src/Configuration/HandlerType.h" +#include "../Machine/MachineConfig.h" +#include "../Protocol.h" // rtAlarm +#include "../Report.h" // hex message +#include "../Configuration/HandlerType.h" +#include "../Platform.h" #include #include #include -const int VFD_RS485_BUF_SIZE = 127; -const int VFD_RS485_QUEUE_SIZE = 10; // number of commands that can be queued up. -const int RESPONSE_WAIT_MS = 1000; // how long to wait for a response -const int VFD_RS485_POLL_RATE = 250; // in milliseconds between commands -const TickType_t response_ticks = RESPONSE_WAIT_MS / portTICK_PERIOD_MS; // in milliseconds between commands - -namespace Spindles { - QueueHandle_t VFD::vfd_cmd_queue = nullptr; - TaskHandle_t VFD::vfd_cmdTaskHandle = nullptr; - - void VFD::reportParsingErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length) { -#ifdef DEBUG_VFD - hex_msg(cmd.msg, "RS485 Tx: ", cmd.tx_length); - hex_msg(rx_message, "RS485 Rx: ", read_length); -#endif - } - void VFD::reportCmdErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length, uint8_t id) { -#ifdef DEBUG_VFD - hex_msg(cmd.msg, "RS485 Tx: ", cmd.tx_length); - hex_msg(rx_message, "RS485 Rx: ", read_length); - - if (read_length != 0) { - if (rx_message[0] != id) { - log_info("RS485 received message from other modbus device"); - } else if (read_length != cmd.rx_length) { - log_info("RS485 received message of unexpected length; expected:" << int(cmd.rx_length) << " got:" << int(read_length)); - } else { - log_info("RS485 CRC check failed"); - } - } else { - log_info("RS485 No response"); - } -#endif - } - - // The communications task - void VFD::vfd_cmd_task(void* pvParameters) { - static bool unresponsive = false; // to pop off a message once each time it becomes unresponsive - static int pollidx = -1; - - VFD* instance = static_cast(pvParameters); - auto& uart = *instance->_uart; - ModbusCommand next_cmd; - uint8_t rx_message[VFD_RS485_MAX_MSG_SIZE]; - bool safetyPollingEnabled = instance->safety_polling(); - - for (; true; delay_ms(VFD_RS485_POLL_RATE)) { - std::atomic_thread_fence(std::memory_order::memory_order_seq_cst); // read fence for settings - response_parser parser = nullptr; - - // First check if we should ask the VFD for the speed parameters as part of the initialization. - if (pollidx < 0 && (parser = instance->initialization_sequence(pollidx, next_cmd)) != nullptr) { - } else { - pollidx = 1; // Done with initialization. Main sequence. - } - next_cmd.critical = false; - - VFDaction action; - if (parser == nullptr) { - // If we don't have a parser, the queue goes first. - if (xQueueReceive(vfd_cmd_queue, &action, 0)) { - switch (action.action) { - case actionSetSpeed: - if (!instance->prepareSetSpeedCommand(action.arg, next_cmd)) { - // prepareSetSpeedCommand() can return false if the speed - // change is unnecessary - already at that speed. - // In that case we just discard the command. - continue; // main loop - } - next_cmd.critical = action.critical; - break; - case actionSetMode: - log_debug("vfd_cmd_task mode:" << action.action); - if (!instance->prepareSetModeCommand(SpindleState(action.arg), next_cmd)) { - continue; // main loop - } - next_cmd.critical = action.critical; - break; - } - } else { - // We do not have a parser and there is nothing in the queue, so we cycle - // through the set of periodic queries. - - // We poll in a cycle. Note that the switch will fall through unless we encounter a hit. - // The weakest form here is 'get_status_ok' which should be implemented if the rest fails. - if (instance->_syncing) { - parser = instance->get_current_speed(next_cmd); - } else if (safetyPollingEnabled) { - switch (pollidx) { - case 1: - parser = instance->get_current_speed(next_cmd); - if (parser) { - pollidx = 2; - break; - } - // fall through if get_current_speed did not return a parser - case 2: - parser = instance->get_current_direction(next_cmd); - if (parser) { - pollidx = 3; - break; - } - // fall through if get_current_direction did not return a parser - case 3: - default: - parser = instance->get_status_ok(next_cmd); - pollidx = 1; - - // we could complete this in case parser == nullptr with some ifs, but let's - // just keep it easy and wait an iteration. - break; - } - } - - // If we have no parser, that means get_status_ok is not implemented (and we have - // nothing resting in our queue). Let's fall back on a simple continue. - if (parser == nullptr) { - continue; // main loop - } - } - } - - // At this point next_cmd has been filled with a command block - { - // Fill in the fields that are the same for all protocol variants - next_cmd.msg[0] = instance->_modbus_id; - - // Grabbed the command. Add the CRC16 checksum: - auto crc16 = ModRTU_CRC(next_cmd.msg, next_cmd.tx_length); - next_cmd.msg[next_cmd.tx_length++] = (crc16 & 0xFF); - next_cmd.msg[next_cmd.tx_length++] = (crc16 & 0xFF00) >> 8; - next_cmd.rx_length += 2; - -#ifdef DEBUG_VFD_ALL - if (parser == nullptr) { - hex_msg(next_cmd.msg, "RS485 Tx: ", next_cmd.tx_length); - } -#endif - } - - // Assume for the worst, and retry... - int retry_count = 0; - for (; retry_count < MAX_RETRIES; ++retry_count) { - // Flush the UART and write the data: - uart.flush(); - uart.write(next_cmd.msg, next_cmd.tx_length); - uart.flushTxTimed(response_ticks); - - // Read the response - size_t read_length = 0; - size_t current_read = uart.timedReadBytes(rx_message, next_cmd.rx_length, response_ticks); - read_length += current_read; - - // Apparently some Huanyang report modbus errors in the correct way, and the rest not. Sigh. - // Let's just check for the condition, and truncate the first byte. - if (read_length > 0 && instance->_modbus_id != 0 && rx_message[0] == 0) { - memmove(rx_message + 1, rx_message, read_length - 1); - } - - while (read_length < next_cmd.rx_length && current_read > 0) { - // Try to read more; we're not there yet... - current_read = uart.timedReadBytes(rx_message + read_length, next_cmd.rx_length - read_length, response_ticks); - read_length += current_read; - } - - // Generate crc16 for the response: - auto crc16response = ModRTU_CRC(rx_message, next_cmd.rx_length - 2); - - if (read_length == next_cmd.rx_length && // check expected length - rx_message[0] == instance->_modbus_id && // check address - rx_message[read_length - 1] == (crc16response & 0xFF00) >> 8 && // check CRC byte 1 - rx_message[read_length - 2] == (crc16response & 0xFF)) { // check CRC byte 1 - - // Success - unresponsive = false; - retry_count = MAX_RETRIES + 1; // stop retry'ing - - // Should we parse this? - if (parser != nullptr) { - if (parser(rx_message, instance)) { - // If we're initializing, move to the next initialization command: - if (pollidx < 0) { - --pollidx; - } - } else { - // Parsing failed - reportParsingErrors(next_cmd, rx_message, read_length); - - // If we were initializing, move back to where we started. - unresponsive = true; - pollidx = -1; // Re-initializing the VFD seems like a plan - log_info("Spindle RS485 did not give a satisfying response"); - } - } - } else { - reportCmdErrors(next_cmd, rx_message, read_length, instance->_modbus_id); - - // Wait a bit before we retry. Set the delay to poll-rate. Not sure - // if we should use a different value... - delay_ms(VFD_RS485_POLL_RATE); - -#ifdef DEBUG_TASK_STACK - static UBaseType_t uxHighWaterMark = 0; - reportTaskStackSize(uxHighWaterMark); -#endif - } - } - - if (retry_count == MAX_RETRIES) { - if (!unresponsive) { - log_info("VFD RS485 Unresponsive"); - unresponsive = true; - pollidx = -1; - } - if (next_cmd.critical) { - mc_critical(ExecAlarm::SpindleControl); - log_error("Critical VFD RS485 Unresponsive"); - } - } - } - } - +namespace Spindles +{ + // number of commands that can be queued up. + const int VFD_RS485_QUEUE_SIZE = 10; + // ================== Class methods ================================== - void VFD::init() { + void VFDSpindle::init() { _sync_dev_speed = 0; _syncing = false; @@ -283,14 +67,14 @@ namespace Spindles { _current_state = SpindleState::Disable; // Initialization is complete, so now it's okay to run the queue task: - if (!vfd_cmd_queue) { // init can happen many times, we only want to start one task - vfd_cmd_queue = xQueueCreate(VFD_RS485_QUEUE_SIZE, sizeof(VFDaction)); - xTaskCreatePinnedToCore(vfd_cmd_task, // task - "vfd_cmdTaskHandle", // name for task - 2048, // size of task stack - this, // parameters - 1, // priority - &vfd_cmdTaskHandle, + if (!VFD::VFDProtocol::vfd_cmd_queue) { // init can happen many times, we only want to start one task + VFD::VFDProtocol::vfd_cmd_queue = xQueueCreate(VFD_RS485_QUEUE_SIZE, sizeof(VFD::VFDProtocol::VFDaction)); + xTaskCreatePinnedToCore(VFD::VFDProtocol::vfd_cmd_task, // task + "vfd_cmdTaskHandle", // name for task + 2048, // size of task stack + this, // parameters + 1, // priority + &VFD::VFDProtocol::vfd_cmdTaskHandle, SUPPORT_TASK_CORE // core ); } @@ -301,11 +85,22 @@ namespace Spindles { set_mode(SpindleState::Disable, true); } - void VFD::config_message() { - _uart->config_message(name(), " Spindle "); + void VFDSpindle::config_message() { _uart->config_message(name(), " Spindle "); } + + void VFDSpindle::set_mode(SpindleState mode, bool critical) { + _last_override_value = sys.spindle_speed_ovr; // sync these on mode changes + if (VFD::VFDProtocol::vfd_cmd_queue) { + VFD::VFDProtocol::VFDaction action; + action.action = VFD::VFDProtocol::actionSetMode; + action.arg = uint32_t(mode); + action.critical = critical; + if (xQueueSend(VFD::VFDProtocol::vfd_cmd_queue, &action, 0) != pdTRUE) { + log_info("VFD Queue Full"); + } + } } - void VFD::setState(SpindleState state, SpindleSpeed speed) { + void VFDSpindle::setState(SpindleState state, SpindleSpeed speed) { log_debug("VFD setState:" << uint8_t(state) << " SpindleSpeed:" << speed); if (sys.abort) { return; // Block during abort. @@ -328,7 +123,7 @@ namespace Spindles { setSpeed(dev_speed); } } - if (use_delay_settings()) { + if (detail_->use_delay_settings()) { spindleDelay(state, speed); } else { // _sync_dev_speed is set by a callback that handles @@ -380,110 +175,42 @@ namespace Spindles { // } } - bool VFD::prepareSetModeCommand(SpindleState mode, ModbusCommand& data) { - // Do variant-specific command preparation - direction_command(mode, data); - - if (mode == SpindleState::Disable) { - if (!xQueueReset(vfd_cmd_queue)) { - log_info(name() << " spindle off, queue could not be reset"); - } - } - - _current_state = mode; - return true; - } - - void VFD::set_mode(SpindleState mode, bool critical) { - _last_override_value = sys.spindle_speed_ovr; // sync these on mode changes - if (vfd_cmd_queue) { - VFDaction action; - action.action = actionSetMode; - action.arg = uint32_t(mode); - action.critical = critical; - if (xQueueSend(vfd_cmd_queue, &action, 0) != pdTRUE) { - log_info("VFD Queue Full"); - } - } - } - - void IRAM_ATTR VFD::setSpeedfromISR(uint32_t dev_speed) { + void IRAM_ATTR VFDSpindle::setSpeedfromISR(uint32_t dev_speed) { if (_current_dev_speed == dev_speed || _last_speed == dev_speed) { return; } _last_speed = dev_speed; - if (vfd_cmd_queue) { - VFDaction action; - action.action = actionSetSpeed; + if (VFD::VFDProtocol::vfd_cmd_queue) { + VFD::VFDProtocol::VFDaction action; + action.action = VFD::VFDProtocol::actionSetSpeed; action.arg = dev_speed; action.critical = (dev_speed == 0); // Ignore errors because reporting is not safe from an ISR. // Perhaps set a flag instead? - xQueueSendFromISR(vfd_cmd_queue, &action, 0); + xQueueSendFromISR(VFD::VFDProtocol::vfd_cmd_queue, &action, 0); } } - void VFD::setSpeed(uint32_t dev_speed) { - if (vfd_cmd_queue) { - VFDaction action; - action.action = actionSetSpeed; + void VFDSpindle::setSpeed(uint32_t dev_speed) { + if (VFD::VFDProtocol::vfd_cmd_queue) { + VFD::VFDProtocol::VFDaction action; + action.action = VFD::VFDProtocol::actionSetSpeed; action.arg = dev_speed; action.critical = dev_speed == 0; - if (xQueueSend(vfd_cmd_queue, &action, 0) != pdTRUE) { + if (xQueueSend(VFD::VFDProtocol::vfd_cmd_queue, &action, 0) != pdTRUE) { log_info("VFD Queue Full"); } } } - bool VFD::prepareSetSpeedCommand(uint32_t speed, ModbusCommand& data) { - log_debug("prep speed " << speed << " curr " << _current_dev_speed); - if (speed == _current_dev_speed) { // prevent setting same speed twice - return false; - } - _current_dev_speed = speed; - -#ifdef DEBUG_VFD_ALL - log_debug("Setting spindle speed to:" << int(speed)); -#endif - // Do variant-specific command preparation - set_speed_command(speed, data); - - // Sometimes sync_dev_speed is retained between different set_speed_command's. We don't want that - we want - // spindle sync to kick in after we set the speed. This forces that. - _sync_dev_speed = UINT32_MAX; - - return true; - } - - // Calculate the CRC on all of the byte except the last 2 - // It then added the CRC to those last 2 bytes - // full_msg_len This is the length of the message including the 2 crc bytes - // Source: https://ctlsys.com/support/how_to_compute_the_modbus_rtu_message_crc/ - uint16_t VFD::ModRTU_CRC(uint8_t* buf, int msg_len) { - uint16_t crc = 0xFFFF; - for (int pos = 0; pos < msg_len; pos++) { - crc ^= uint16_t(buf[pos]); // XOR byte into least sig. byte of crc. - - for (int i = 8; i != 0; i--) { // Loop over each bit - if ((crc & 0x0001) != 0) { // If the LSB is set - crc >>= 1; // Shift right and XOR 0xA001 - crc ^= 0xA001; - } else { // Else LSB is not set - crc >>= 1; // Just shift right - } - } - } - - return crc; - } - void VFD::validate() { + void VFDSpindle::validate() { Spindle::validate(); Assert(_uart != nullptr || _uart_num != -1, "VFD: missing UART configuration"); } - void VFD::group(Configuration::HandlerBase& handler) { + void VFDSpindle::group(Configuration::HandlerBase& handler) { if (handler.handlerType() == Configuration::HandlerType::Generator) { if (_uart_num == -1) { handler.section("uart", _uart, 1); diff --git a/FluidNC/src/Spindles/VFDSpindle.h b/FluidNC/src/Spindles/VFDSpindle.h index 52a794858..5ca45cd55 100644 --- a/FluidNC/src/Spindles/VFDSpindle.h +++ b/FluidNC/src/Spindles/VFDSpindle.h @@ -14,61 +14,28 @@ namespace Spindles { extern Uart _uart; - - class VFD : public Spindle { + + + namespace VFD { + // VFDProtocol resides in a separate class because it doesn't need to be in IRAM. This contains all the + // VFD specific code, which is called from a separate task. + class VFDProtocol; + } + + // VFD base class. Called by the stepper engine. Normally you don't want to touch this. + class VFDSpindle : public Spindle { private: - static const int VFD_RS485_MAX_MSG_SIZE = 16; // more than enough for a modbus message - static const int MAX_RETRIES = 5; // otherwise the spindle is marked 'unresponsive' + friend class Spindles::VFD::VFDProtocol; - void set_mode(SpindleState mode, bool critical); + VFD::VFDProtocol* detail_ = nullptr; int32_t _current_dev_speed = -1; uint32_t _last_speed = 0; Percent _last_override_value = 100; // no override is 100 percent - static QueueHandle_t vfd_cmd_queue; - static TaskHandle_t vfd_cmdTaskHandle; - static void vfd_cmd_task(void* pvParameters); - - static uint16_t ModRTU_CRC(uint8_t* buf, int msg_len); - enum VFDactionType : uint8_t { actionSetSpeed, actionSetMode }; - struct VFDaction { - VFDactionType action; - bool critical; - uint32_t arg; - }; - - protected: - struct ModbusCommand { - bool critical; // TODO SdB: change into `uint8_t critical : 1;`: We want more flags... - - uint8_t tx_length; - uint8_t rx_length; - uint8_t msg[VFD_RS485_MAX_MSG_SIZE]; - }; - - private: - bool prepareSetModeCommand(SpindleState mode, ModbusCommand& data); - bool prepareSetSpeedCommand(uint32_t speed, ModbusCommand& data); - - static void reportParsingErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length); - static void reportCmdErrors(ModbusCommand cmd, uint8_t* rx_message, size_t read_length, uint8_t id); + void set_mode(SpindleState mode, bool critical); protected: - // Commands: - virtual void direction_command(SpindleState mode, ModbusCommand& data) = 0; - virtual void set_speed_command(uint32_t rpm, ModbusCommand& data) = 0; - - // Commands that return the status. Returns nullptr if unavailable by this VFD (default): - using response_parser = bool (*)(const uint8_t* response, VFD* spindle); - - virtual response_parser initialization_sequence(int index, ModbusCommand& data) { return nullptr; } - virtual response_parser get_current_speed(ModbusCommand& data) { return nullptr; } - virtual response_parser get_current_direction(ModbusCommand& data) { return nullptr; } - virtual response_parser get_status_ok(ModbusCommand& data) = 0; - virtual bool safety_polling() const { return true; } - bool use_delay_settings() const override { return true; } - // The constructor sets these int _uart_num = -1; Uart* _uart = nullptr; @@ -79,11 +46,11 @@ namespace Spindles { volatile bool _syncing; public: - VFD(const char* name) : Spindle(name) {} - VFD(const VFD&) = delete; - VFD(VFD&&) = delete; - VFD& operator=(const VFD&) = delete; - VFD& operator=(VFD&&) = delete; + VFDSpindle(const char* name, VFD::VFDProtocol* detail) : Spindle(name), detail_(detail) {} + VFDSpindle(const VFDSpindle&) = delete; + VFDSpindle(VFDSpindle&&) = delete; + VFDSpindle& operator=(const VFDSpindle&) = delete; + VFDSpindle& operator=(VFDSpindle&&) = delete; void init(); void config_message(); @@ -97,6 +64,6 @@ namespace Spindles { void validate() override; void group(Configuration::HandlerBase& handler) override; - virtual ~VFD() {} + virtual ~VFDSpindle() {} }; } diff --git a/FluidNC/src/Spindles/YL620Spindle.cpp b/FluidNC/src/Spindles/YL620Spindle.cpp deleted file mode 100644 index 59adc6588..000000000 --- a/FluidNC/src/Spindles/YL620Spindle.cpp +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2021 - Marco Wagner -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -/* - This is for a Yalang YL620/YL620-A VFD based spindle to be controlled via RS485 Modbus RTU. - - WARNING!!!! - VFDs are very dangerous. They have high voltages and are very powerful - Remove power before changing bits. - - ============================================================================================================= - A Chinese manual for Modbus communication to YL620 can be found at - https://docs.google.com/document/d/1TkERAvHZby4uad_i9kSk19HlDhbM7_xd/edit - You can use Google Translate to translate it. - - Only Modbus RTU mode is supported, not Modbus ASCII mode. - - Manual Configuration required for the YL620 - - Parameter number Description Value - ------------------------------------------------------------------------------- - P00.00 Main frequency 400.00Hz (match to your spindle) - P00.01 Command source 3 - - P03.00 RS485 Baud rate 3 (9600) - P03.01 RS485 address 1 - P03.02 RS485 protocol 2 - P03.08 Frequency given lower limit 100.0Hz (match to your spindle cooling-type) - - =============================================================================================================== - - RS485 communication is standard Modbus RTU - - Therefore, the following operation codes are relevant: - 0x03: read single holding register - 0x06: write single holding register - - Given a parameter Pnn.mm, the high byte of the register address is nn, - the low is mm. The numbers nn and mm in the manual are given in decimal, - so P13.16 would be register address 0x0d10 when represented in hex. - - Holding register address Description - --------------------------------------------------------------------------- - 0x0000 main frequency - 0x0308 frequency given lower limit - - 0x2000 command register (further information below) - 0x2001 Modbus485 frequency command (x0.1Hz => 2500 = 250.0Hz) - - 0x200A Target frequency - 0x200B Output frequency - 0x200C Output current - - - Command register at holding address 0x2000 - -------------------------------------------------------------------------- - bit 1:0 b00: No function - b01: shutdown command - b10: start command - b11: Jog command - bit 3:2 reserved - bit 5:4 b00: No function - b01: Forward command - b10: Reverse command - b11: change direction - bit 7:6 b00: No function - b01: reset an error flag - b10: reset all error flags - b11: reserved -*/ - -#include "YL620Spindle.h" - -#include - -namespace Spindles { - void YL620::direction_command(SpindleState mode, ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 6; - - // data.msg[0] is omitted (modbus address is filled in later) - data.msg[1] = 0x06; // 06: write output register - data.msg[2] = 0x20; // 0x2000: command register address - data.msg[3] = 0x00; - - data.msg[4] = 0x00; // High-Byte of command always 0x00 - switch (mode) { - case SpindleState::Cw: - data.msg[5] = 0x12; // Start in forward direction - break; - case SpindleState::Ccw: - data.msg[5] = 0x22; // Start in reverse direction - break; - default: // SpindleState::Disable - data.msg[5] = 0x01; // Disable spindle - break; - } - } - - void IRAM_ATTR YL620::set_speed_command(uint32_t speed, ModbusCommand& data) { -#ifdef DEBUG_VFD - log_debug("Setting VFD speed to " << speed); -#endif - - data.tx_length = 6; - data.rx_length = 6; - - data.msg[1] = 0x06; - data.msg[2] = 0x20; - data.msg[3] = 0x01; - data.msg[4] = speed >> 8; - data.msg[5] = speed & 0xFF; - } - - VFD::response_parser YL620::initialization_sequence(int index, ModbusCommand& data) { - if (index == -1) { - data.tx_length = 6; - data.rx_length = 5; - - data.msg[1] = 0x03; - data.msg[2] = 0x03; - data.msg[3] = 0x08; - data.msg[4] = 0x00; - data.msg[5] = 0x01; - - // Recv: 01 03 02 03 E8 xx xx - // -- -- = 1000 - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - auto yl620 = static_cast(vfd); - yl620->_minFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - -#ifdef DEBUG_VFD - log_debug("YL620 allows minimum frequency of:" << yl620->_minFrequency << " Hz"); -#endif - - return true; - }; - } else if (index == -2) { - data.tx_length = 6; - data.rx_length = 5; - - data.msg[1] = 0x03; - data.msg[2] = 0x00; - data.msg[3] = 0x00; - data.msg[4] = 0x00; - data.msg[5] = 0x01; - - // Recv: 01 03 02 0F A0 xx xx - // -- -- = 4000 - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - auto yl620 = static_cast(vfd); - yl620->_maxFrequency = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - - // frequency is in Hz * 10, so RPM is frequency * 60 / 10 = frequency * 6 - // E.g. for 400 Hz, we have frequency = 4000, so 4000 * 6 = 24000 RPM - - if (vfd->_speeds.size() == 0) { - // Convert from frequency in deciHz to RPM (*60/10) - SpindleSpeed maxRPM = yl620->_maxFrequency * 6; - SpindleSpeed minRPM = yl620->_minFrequency * 6; - - vfd->shelfSpeeds(minRPM, maxRPM); - } - - vfd->setupSpeeds(yl620->_maxFrequency); - vfd->_slop = std::max(yl620->_maxFrequency / 40, 1); - - // vfd->_min_rpm = uint32_t(vfd->_max_rpm) * uint32_t(yl620->_minFrequency) / - // uint32_t(yl620->_maxFrequency); // 1000 * 24000 / 4000 = 6000 RPM. - -#ifdef DEBUG_VFD - log_debug("YL620 allows maximum frequency " << yl620->_maxFrequency << " Hz"); -#endif - - return true; - }; - } else { - return nullptr; - } - } - - VFD::response_parser YL620::get_current_speed(ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 5; - - // Send: 01 03 200B 0001 - data.msg[1] = 0x03; - data.msg[2] = 0x20; - data.msg[3] = 0x0B; - data.msg[4] = 0x00; - data.msg[5] = 0x01; - - // Recv: 01 03 02 05 DC xx xx - // ---- = 1500 - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { - uint16_t freq = (uint16_t(response[3]) << 8) | uint16_t(response[4]); - - auto yl620 = static_cast(vfd); - - vfd->_sync_dev_speed = freq; - return true; - }; - } - - VFD::response_parser YL620::get_current_direction(ModbusCommand& data) { - data.tx_length = 6; - data.rx_length = 5; - - // Send: 01 03 20 00 00 01 - data.msg[1] = 0x03; - data.msg[2] = 0x20; - data.msg[3] = 0x00; - data.msg[4] = 0x00; - data.msg[5] = 0x01; - - // Receive: 01 03 02 00 0A xx xx - // ----- status is in 00 0A bit 5:4 - - // TODO: What are we going to do with this? Update vfd state? - return [](const uint8_t* response, Spindles::VFD* vfd) -> bool { return true; }; - } - - // Configuration registration - namespace { - SpindleFactory::InstanceBuilder registration("YL620"); - } -} diff --git a/FluidNC/src/Spindles/YL620Spindle.h b/FluidNC/src/Spindles/YL620Spindle.h deleted file mode 100644 index 49acc239b..000000000 --- a/FluidNC/src/Spindles/YL620Spindle.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2021 - Marco Wagner -// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file. - -#pragma once - -#include "VFDSpindle.h" - -namespace Spindles { - class YL620 : public VFD { - protected: - uint16_t _minFrequency = 0; // frequency lower limit. Factor 10 of actual frequency - uint16_t _maxFrequency = 4000; // max frequency the VFD will allow. Normally 400.0. Factor 10 of actual frequency - - void direction_command(SpindleState mode, ModbusCommand& data) override; - void set_speed_command(uint32_t rpm, ModbusCommand& data) override; - - response_parser initialization_sequence(int index, ModbusCommand& data) override; - response_parser get_current_speed(ModbusCommand& data) override; - response_parser get_current_direction(ModbusCommand& data) override; - response_parser get_status_ok(ModbusCommand& data) override { return nullptr; } - - bool safety_polling() const override { return false; } - - public: - YL620(const char* name) : VFD(name) {} - }; -} diff --git a/generate_vcxproj.py b/generate_vcxproj.py index 64ece9536..884baa3fc 100644 --- a/generate_vcxproj.py +++ b/generate_vcxproj.py @@ -47,7 +47,7 @@ class Vcxproj: # configuration, platform ImportGroupFmt = '\n'.join([ ' ', - ' ', + ' ', ' ' ]) @@ -192,12 +192,12 @@ def CreateProject(self): project.append(' Win32Proj') project.append('') - project.append('') + project.append('') for p in self.Platforms: for c in self.Configurations: project.append(Vcxproj.ConfigTypePropertyGroup(c, p)) - project.append('') + project.append('') project.append('') project.append('') project.append(' ') @@ -214,7 +214,7 @@ def CreateProject(self): project.append('') project.append('') - project.append('') + project.append('') project.append(' ') project.append('') project.append('') From 396fb41fd6053294ac3b1873c5e9b9734089563b Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Sun, 27 Oct 2024 07:12:17 -1000 Subject: [PATCH 28/30] Fix #1361 - changed RR to OR in expressions --- FluidNC/src/Expression.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FluidNC/src/Expression.cpp b/FluidNC/src/Expression.cpp index 814ae1edd..0096947b6 100644 --- a/FluidNC/src/Expression.cpp +++ b/FluidNC/src/Expression.cpp @@ -357,7 +357,7 @@ std::map> binary_ops = { { "]", Binary_RightBracket }, { "AND", Binary_And2 }, { "MOD", Binary_Mod }, - { "RR", Binary_NotExclusiveOr }, + { "OR", Binary_NotExclusiveOr }, { "XOR", Binary_ExclusiveOr }, { "EQ", Binary_EQ }, { "NE", Binary_NE }, @@ -438,7 +438,7 @@ static Error read_operation(const char* line, size_t& pos, ngc_binary_op_t& oper status = Error::ExpressionUnknownOp; // Unknown operation name starting with M break; - case 'R': + case 'O': if (line[pos] == 'R') { operation = Binary_NotExclusiveOR; pos++; From e91ae7a9f6a358a78f3316ed4e8ed1095dd1577e Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Mon, 28 Oct 2024 10:55:54 -1000 Subject: [PATCH 29/30] Omit unnecessary cs_pin toggling in TMC2209 driver --- FluidNC/src/Motors/TMC2209Driver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FluidNC/src/Motors/TMC2209Driver.cpp b/FluidNC/src/Motors/TMC2209Driver.cpp index 97b614459..79e11d4c1 100644 --- a/FluidNC/src/Motors/TMC2209Driver.cpp +++ b/FluidNC/src/Motors/TMC2209Driver.cpp @@ -113,13 +113,13 @@ namespace MotorDrivers { } void TMC2209Driver::set_disable(bool disable) { - _cs_pin.synchronousWrite(true); if (TrinamicUartDriver::startDisable(disable)) { if (_use_enable) { + _cs_pin.synchronousWrite(true); tmc2209->toff(TrinamicUartDriver::toffValue()); + _cs_pin.synchronousWrite(false); } } - _cs_pin.synchronousWrite(false); } bool TMC2209Driver::test() { From bbba7f35b22f3b4150704f7c30a237235cbfc69f Mon Sep 17 00:00:00 2001 From: Mitch Bradley Date: Mon, 28 Oct 2024 10:56:31 -1000 Subject: [PATCH 30/30] Fix I2S bug that prevented some TMC2209s from configuring on Jackpot --- FluidNC/esp32/i2s_engine.c | 81 ++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/FluidNC/esp32/i2s_engine.c b/FluidNC/esp32/i2s_engine.c index 4be514616..e80db4ad2 100644 --- a/FluidNC/esp32/i2s_engine.c +++ b/FluidNC/esp32/i2s_engine.c @@ -34,7 +34,7 @@ /* 32-bit mode: 1000000 usec / ((160000000 Hz) / 5 / 2) x 32 bit/pulse x 2(stereo) = 4 usec/pulse */ const uint32_t I2S_OUT_USEC_PER_PULSE = 2; -static uint32_t i2s_out_port_data = 0; +static volatile uint32_t i2s_out_port_data = 0; static int i2s_out_initialized = 0; @@ -136,13 +136,37 @@ static int i2s_out_start() { return 0; } +// The key FIFO parameters are FIFO_THRESHOLD and FIFO_RELOAD +// Their sum must be less than FIFO_LENGTH (64 for ESP32). +// - FIFO_THRESHOLD is the level at which the interrupt fires. +// If it is too low, you risk FIFO underflow. Higher values +// allow more leeway for interrupt latency, but increase the +// latency between the software step generation and the appearance +// of step pulses at the driver. +// - FIFO_RELOAD is the number of entries that each ISR invocation +// pushes into the FIFO. Larger values of FIFO_RELOAD decrease +// the number of times that the ISR runs, while smaller values +// decrease the step generation latency. +// - With an I2S frame clock of 500 kHz, FIFO_THRESHOLD = 16, +// FIFO_RELOAD = 8, the step latency is about 24 us. That +// is about half of the modulation period of a laser that +// is modulated at 20 kHZ. + +#define FIFO_LENGTH (I2S_TX_DATA_NUM + 1) +#define FIFO_THRESHOLD (FIFO_LENGTH / 4) +#define FIFO_REMAINING (FIFO_LENGTH - FIFO_THRESHOLD) +#define FIFO_RELOAD 8 + +static bool timer_running = false; + // -// External funtions +// External functions // void i2s_out_delay() { // Depending on the timing, it may not be reflected immediately, // so wait twice as long just in case. - delay_us(I2S_OUT_USEC_PER_PULSE * 2); + uint32_t wait_counts = timer_running ? FIFO_THRESHOLD + FIFO_RELOAD : 2; + delay_us(I2S_OUT_USEC_PER_PULSE * wait_counts); } void IRAM_ATTR i2s_out_write(pinnum_t pin, uint8_t val) { @@ -152,6 +176,11 @@ void IRAM_ATTR i2s_out_write(pinnum_t pin, uint8_t val) { } else { i2s_out_port_data &= ~bit; } + + if (!timer_running) { + // Direct write to the I2S FIFO in case the pulse timer is not running + I2S0.fifo_wr = i2s_out_port_data; + } } uint8_t i2s_out_read(pinnum_t pin) { @@ -301,27 +330,6 @@ int i2s_out_init(i2s_out_init_t* init_param) { static uint32_t _pulse_counts = 2; static uint32_t _dir_delay_us; -// The key FIFO parameters are FIFO_THRESHOLD and FIFO_RELOAD -// Their sum must be less than FIFO_LENGTH (64 for ESP32). -// - FIFO_THRESHOLD is the level at which the interrupt fires. -// If it is too low, you risk FIFO underflow. Higher values -// allow more leeway for interrupt latency, but increase the -// latency between the software step generation and the appearance -// of step pulses at the driver. -// - FIFO_RELOAD is the number of entries that each ISR invocation -// pushes into the FIFO. Larger values of FIFO_RELOAD decrease -// the number of times that the ISR runs, while smaller values -// decrease the step generation latency. -// - With an I2S frame clock of 500 kHz, FIFO_THRESHOLD = 16, -// FIFO_RELOAD = 8, the step latency is about 24 us. That -// is about half of the modulation period of a laser that -// is modulated at 20 kHZ. - -#define FIFO_LENGTH (I2S_TX_DATA_NUM + 1) -#define FIFO_THRESHOLD (FIFO_LENGTH / 4) -#define FIFO_REMAINING (FIFO_LENGTH - FIFO_THRESHOLD) -#define FIFO_RELOAD 8 - bool (*_pulse_func)(); static uint32_t _remaining_pulse_counts = 0; @@ -338,22 +346,23 @@ static void IRAM_ATTR set_timer_ticks(uint32_t ticks) { } static void IRAM_ATTR start_timer() { - static bool once = true; - if (once) { + if (!timer_running) { i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 1); i2s_ll_clear_intr_status(&I2S0, I2S_PUT_DATA_INT_CLR); - once = false; + timer_running = true; } } static void IRAM_ATTR stop_timer() { - i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 0); + if (timer_running) { + i2s_ll_enable_intr(&I2S0, I2S_TX_PUT_DATA_INT_ENA, 0); + timer_running = false; + } } static void IRAM_ATTR i2s_isr() { // gpio_write(12, 1); // For debugging // Keeping local copies of this information speeds up the ISR - uint32_t delay_data = i2s_out_port_data; uint32_t pulse_data = _pulse_data; uint32_t remaining_pulse_counts = _remaining_pulse_counts; uint32_t remaining_delay_counts = _remaining_delay_counts; @@ -365,21 +374,19 @@ static void IRAM_ATTR i2s_isr() { --i; --remaining_pulse_counts; } else if (remaining_delay_counts) { - I2S0.fifo_wr = delay_data; + I2S0.fifo_wr = i2s_out_port_data; --i; --remaining_delay_counts; } else { - // Set _pulse_data to a safe value in case pulse_func() does nothing, + // Set _pulse_data to the non-pulse value in case pulse_func() does nothing, // which can happen if it is not awake _pulse_data = i2s_out_port_data; _pulse_func(); // Reload from variables that could have been modified by pulse_func - pulse_data = _pulse_data; - delay_data = i2s_out_port_data; - - remaining_pulse_counts = pulse_data == delay_data ? 0 : _pulse_counts; + pulse_data = _pulse_data; + remaining_pulse_counts = pulse_data == i2s_out_port_data ? 0 : _pulse_counts; remaining_delay_counts = _delay_counts - remaining_pulse_counts; } } while (i); @@ -426,6 +433,10 @@ static uint32_t init_engine(uint32_t dir_delay_us, uint32_t pulse_us, uint32_t f // gpio_mode(12, 0, 1, 0, 0, 0); + // Run the pulser all the time to pick up writes to non-stepping I2S outputs + start_timer(); + set_timer_ticks(100); + return _pulse_counts * I2S_OUT_USEC_PER_PULSE; }