/// @file motion.h #pragma once #include "../pins.h" #include "pulse_gen.h" #include "axisunit.h" namespace modules { /// Logic of motor handling /// Ideally enable stepping of motors under ISR (all timers have higher priority than serial) namespace motion { // Import axes definitions using config::NUM_AXIS; using namespace hal::tmc2130; using pulse_gen::st_timer_t; // Check for configuration invariants static_assert( (1. / (F_CPU / config::stepTimerFrequencyDivider) * config::stepTimerQuantum) > (1. / config::maxStepFrequency), "stepTimerQuantum must be larger than the maximal stepping frequency interval"); /// Main axis enumeration struct AxisParams { char name; MotorParams params; MotorCurrents currents; MotorMode mode; steps_t jerk; steps_t accel; }; /// Return the default motor mode for an Axis static constexpr MotorMode DefaultMotorMode(const config::AxisConfig &axis) { return axis.stealth ? MotorMode::Stealth : MotorMode::Normal; } /// Static axis configuration static AxisParams axisParams[NUM_AXIS] = { // Pulley { .name = 'P', .params = { .spi = hal::spi::TmcSpiBus, .idx = Pulley, .dirOn = config::pulley.dirOn, .csPin = PULLEY_CS_PIN, .stepPin = PULLEY_STEP_PIN, .sgPin = PULLEY_SG_PIN, .mRes = config::pulley.mRes }, .currents = { .vSense = config::pulley.vSense, .iRun = config::pulley.iRun, .iHold = config::pulley.iHold }, .mode = DefaultMotorMode(config::pulley), .jerk = unitToSteps(config::pulleyLimits.jerk), .accel = unitToSteps(config::pulleyLimits.accel), }, // Selector { .name = 'S', .params = { .spi = hal::spi::TmcSpiBus, .idx = Selector, .dirOn = config::selector.dirOn, .csPin = SELECTOR_CS_PIN, .stepPin = SELECTOR_STEP_PIN, .sgPin = SELECTOR_SG_PIN, .mRes = config::selector.mRes }, .currents = { .vSense = config::selector.vSense, .iRun = config::selector.iRun, .iHold = config::selector.iHold }, .mode = DefaultMotorMode(config::selector), .jerk = unitToSteps(config::selectorLimits.jerk), .accel = unitToSteps(config::selectorLimits.accel), }, // Idler { .name = 'I', .params = { .spi = hal::spi::TmcSpiBus, .idx = Idler, .dirOn = config::idler.dirOn, .csPin = IDLER_CS_PIN, .stepPin = IDLER_STEP_PIN, .sgPin = IDLER_SG_PIN, .mRes = config::idler.mRes }, .currents = { .vSense = config::idler.vSense, .iRun = config::idler.iRun, .iHold = config::idler.iHold }, .mode = DefaultMotorMode(config::idler), .jerk = unitToSteps(config::idlerLimits.jerk), .accel = unitToSteps(config::idlerLimits.accel), }, }; class Motion { public: inline constexpr Motion() = default; /// Init axis driver - @@TODO this should be probably hidden /// somewhere deeper ... something should manage the axes and their /// state especially when the TMC may get randomly reset (deinited) /// @returns true if the init was successful (TMC2130 responded ok) bool InitAxis(Axis axis); /// Return the axis power status. bool Enabled(Axis axis) const { return axisData[axis].enabled; } /// Set axis power status. One must manually ensure no moves are currently being /// performed by calling QueueEmpty(). void SetEnabled(Axis axis, bool enabled); /// Disable axis motor. One must manually ensure no moves are currently being /// performed by calling QueueEmpty(). void Disable(Axis axis) { SetEnabled(axis, false); } /// Set mode of TMC/motors operation. One must manually ensure no moves are currently /// being performed by calling QueueEmpty(). void SetMode(Axis axis, MotorMode mode); /// Set the same mode of TMC/motors operation for all axes. @see SetMode void SetMode(MotorMode mode); /// @returns true if a stall guard event occurred recently on the axis bool StallGuard(Axis axis); /// clear stall guard flag reported on an axis void StallGuardReset(Axis axis); /// Enqueue a single axis move in steps starting and ending at zero speed with maximum /// feedrate. Moves can only be enqueued if the axis is not Full(). /// @param axis axis affected /// @param pos target position /// @param feed_rate maximum feedrate /// @param end_rate endding feedrate (may not be reached!) void PlanMoveTo(Axis axis, pos_t pos, steps_t feed_rate, steps_t end_rate = 0); /// Enqueue a single axis move using PlanMoveTo, but using AxisUnit. The Axis needs to /// be supplied as the first template argument: PlanMoveTo(pos, rate). /// @see PlanMoveTo, unitToSteps template constexpr void PlanMoveTo(AxisUnit pos, AxisUnit feed_rate, AxisUnit end_rate = { 0 }) { PlanMoveTo(A, pos.v, feed_rate.v, end_rate.v); } /// Enqueue a single axis move using PlanMoveTo, but using physical units. The Axis /// needs to be supplied as the first template argument: PlanMoveTo(pos, rate). /// @see PlanMoveTo, unitToSteps template constexpr void PlanMoveTo(config::Unit pos, config::Unit feed_rate, config::Unit end_rate = { 0 }) { PlanMoveTo( unitToAxisUnit>(pos), unitToAxisUnit>(feed_rate), unitToAxisUnit>(end_rate)); } /// Enqueue a single axis move in steps starting and ending at zero speed with maximum /// feedrate. Moves can only be enqueued if the axis is not Full(). /// @param axis axis affected /// @param delta relative to current position /// @param feed_rate maximum feedrate /// @param end_rate endding feedrate (may not be reached!) void PlanMove(Axis axis, pos_t delta, steps_t feed_rate, steps_t end_rate = 0) { PlanMoveTo(axis, Position(axis) + delta, feed_rate, end_rate); } /// Enqueue a single axis move using PlanMove, but using AxisUnit. The Axis needs to /// be supplied as the first template argument: PlanMove(pos, rate). /// @see PlanMove, unitToSteps template constexpr void PlanMove(AxisUnit delta, AxisUnit feed_rate, AxisUnit end_rate = { 0 }) { PlanMove(A, delta.v, feed_rate.v, end_rate.v); } /// Enqueue a single axis move using PlanMove, but using physical units. The Axis needs to /// be supplied as the first template argument: PlanMove(pos, rate). /// @see PlanMove, unitToSteps template constexpr void PlanMove(config::Unit delta, config::Unit feed_rate, config::Unit end_rate = { 0 }) { PlanMove( unitToAxisUnit>(delta), unitToAxisUnit>(feed_rate), unitToAxisUnit>(end_rate)); } /// @returns head position of an axis (last enqueued position) /// @param axis axis affected pos_t Position(Axis axis) const; /// Fetch the current position of the axis while stepping. This function is expensive! /// It's necessary only in exceptional cases. For regular usage, Position() should /// probably be used instead. /// @param axis axis affected /// @returns the current position of the axis pos_t CurPosition(Axis axis) const { return axisData[axis].ctrl.CurPosition(); } /// Set the position of an axis. Should only be called when the queue is empty. /// @param axis axis affected /// @param x position to set void SetPosition(Axis axis, pos_t x) { axisData[axis].ctrl.SetPosition(x); } /// Get current acceleration for the selected axis /// @param axis axis affected /// @returns acceleration steps_t Acceleration(Axis axis) const { return axisData[axis].ctrl.Acceleration(); } /// Set acceleration for the selected axis /// @param axis axis affected /// @param accel acceleration void SetAcceleration(Axis axis, steps_t accel) { axisData[axis].ctrl.SetAcceleration(accel); } /// Set acceleration for the selected axis, but using AxisUnit. The Axis needs to /// be supplied as the first template argument: SetAcceleration(accel). /// @see SetAcceleration, unitToSteps template void SetAcceleration(AxisUnit accel) { SetAcceleration(A, accel.v); } /// Set acceleration for the selected axis, but using physical units. The Axis needs to /// be supplied as the first template argument: SetAcceleration(accel). /// @tparam A axis affected /// @tparam B unit base for the axis /// @param accel acceleration template void SetAcceleration(config::Unit accel) { SetAcceleration(unitToAxisUnit>(accel)); } /// Get current jerk for the selected axis /// @param axis axis affected /// @returns jerk steps_t Jerk(Axis axis) const { return axisData[axis].ctrl.Jerk(); } /// Set maximum jerk for the selected axis /// @param axis axis affected /// @param max_jerk maximum jerk void SetJerk(Axis axis, steps_t max_jerk) { return axisData[axis].ctrl.SetJerk(max_jerk); } /// Fetch the target rate of the last planned segment for the requested axis, or the /// current effective rate when the move has been aborted. /// @param axis axis affected /// @returns last rate steps_t Rate(Axis axis) const { return axisData[axis].ctrl.Rate(); } /// State machine doing all the planning and stepping. Called by the stepping ISR. /// @returns the interval for the next tick #if !defined(UNITTEST) || defined(UNITTEST_MOTION) inline st_timer_t Step() { st_timer_t timers[NUM_AXIS]; // step and calculate interval for each new move for (uint8_t i = 0; i != NUM_AXIS; ++i) { timers[i] = axisData[i].residual; if (timers[i] <= config::stepTimerQuantum) { if (timers[i] || !axisData[i].ctrl.QueueEmpty()) { if (st_timer_t next = axisData[i].ctrl.Step(axisParams[i].params)) { timers[i] += next; // axis has been moved, run the tmc2130 Isr for this axis axisData[i].drv.Isr(axisParams[i].params); } } } } // plan next closest interval st_timer_t next = timers[0]; for (uint8_t i = 1; i != NUM_AXIS; ++i) { if (timers[i] && (!next || timers[i] < next)) next = timers[i]; } // update residuals for (uint8_t i = 0; i != NUM_AXIS; ++i) { axisData[i].residual = (timers[i] ? timers[i] - next : 0); } return next; } #else // Force STUB for testing st_timer_t Step(); #endif /// @returns true if all planned moves have been finished for all axes bool QueueEmpty() const; /// @returns true if all planned moves have been finished for one axis /// @param axis requested #if !defined(UNITTEST) || defined(UNITTEST_MOTION) bool QueueEmpty(Axis axis) const { return axisData[axis].ctrl.QueueEmpty(); } #else // Force STUB for testing bool QueueEmpty(Axis axis) const; #endif /// @returns false if new moves can still be planned for one axis /// @param axis axis requested bool Full(Axis axis) const { return axisData[axis].ctrl.Full(); } /// Stop whatever moves are being done for one axis /// @param axis axis requested /// @param halt When true, also abruptly stop axis movement. void AbortPlannedMoves(Axis axis, bool halt = true); /// Stop whatever moves are being done on all axes /// @param halt When true, also abruptly stop axis movement. void AbortPlannedMoves(bool halt = true); /// @returns the TMC213 driver associated with the particular axis inline const hal::tmc2130::TMC2130 &DriverForAxis(Axis axis) const { return axisData[axis].drv; } private: struct AxisData { TMC2130 drv; ///< Motor driver pulse_gen::PulseGen ctrl; ///< Motor controller bool enabled; ///< Axis enabled st_timer_t residual; ///< Axis timer residual }; /// Helper to initialize AxisData members static AxisData DataForAxis(Axis axis) { return { .drv = {}, .ctrl = { axisParams[axis].jerk, axisParams[axis].accel, }, .enabled = false }; } /// Dynamic axis data AxisData axisData[NUM_AXIS] = { DataForAxis(Pulley), DataForAxis(Selector), DataForAxis(Idler), }; }; /// ISR initialization extern void Init(); extern Motion motion; } // namespace motion } // namespace modules namespace mm = modules::motion;