Firmware2/Marlin/stepper.cpp
AnHardt f3eee02596 Introduce endstop interrupts
If ENDSTOP_INTERRUPTS_FEATURE is enabled this tries to set up interrupt routines
for all used endstop pins. If this worked without errors, `endstops.update()` is called
only if one of the endstops changed its state.

The new interrupt routines do not really check the endstops and react upon them. All what they
do, is to set a flag if it makes sense to call the endstop test we are used to.

This can be used on:
* ARM (DUE) based boards - all pins can raise interrupts,
* RAMPS - all 6 endstop pins plus some other on EXT-2 can raise interrupts,
* RAMPS based boards - as long the designers did not change the pins for the endstops or at least left enough,
* all boards, if there are enough pins that can raise interrupts, and you are willing/able to swap with pins dedicated to other purpose.
2016-11-15 12:07:41 +01:00

1363 lines
39 KiB
C++

/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* stepper.cpp - A singleton object to execute motion plans using stepper motors
* Marlin Firmware
*
* Derived from Grbl
* Copyright (c) 2009-2011 Simen Svale Skogsrud
*
* Grbl is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Grbl is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
/* The timer calculations of this module informed by the 'RepRap cartesian firmware' by Zack Smith
and Philipp Tiefenbacher. */
#include "Marlin.h"
#include "stepper.h"
#include "endstops.h"
#include "planner.h"
#include "temperature.h"
#include "ultralcd.h"
#include "language.h"
#include "cardreader.h"
#include "speed_lookuptable.h"
#if HAS_DIGIPOTSS
#include <SPI.h>
#endif
Stepper stepper; // Singleton
// public:
block_t* Stepper::current_block = NULL; // A pointer to the block currently being traced
#if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED)
bool Stepper::abort_on_endstop_hit = false;
#endif
#if ENABLED(Z_DUAL_ENDSTOPS)
bool Stepper::performing_homing = false;
#endif
// private:
unsigned char Stepper::last_direction_bits = 0; // The next stepping-bits to be output
unsigned int Stepper::cleaning_buffer_counter = 0;
#if ENABLED(Z_DUAL_ENDSTOPS)
bool Stepper::locked_z_motor = false;
bool Stepper::locked_z2_motor = false;
#endif
long Stepper::counter_X = 0,
Stepper::counter_Y = 0,
Stepper::counter_Z = 0,
Stepper::counter_E = 0;
volatile uint32_t Stepper::step_events_completed = 0; // The number of step events executed in the current block
#if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
unsigned char Stepper::old_OCR0A = 0;
volatile unsigned char Stepper::eISR_Rate = 200; // Keep the ISR at a low rate until needed
#if ENABLED(LIN_ADVANCE)
volatile int Stepper::e_steps[E_STEPPERS];
int Stepper::final_estep_rate,
Stepper::current_estep_rate[E_STEPPERS],
Stepper::current_adv_steps[E_STEPPERS];
#else
long Stepper::e_steps[E_STEPPERS],
Stepper::final_advance = 0,
Stepper::old_advance = 0,
Stepper::advance_rate,
Stepper::advance;
#endif
#endif
long Stepper::acceleration_time, Stepper::deceleration_time;
volatile long Stepper::count_position[NUM_AXIS] = { 0 };
volatile signed char Stepper::count_direction[NUM_AXIS] = { 1, 1, 1, 1 };
#if ENABLED(MIXING_EXTRUDER)
long Stepper::counter_m[MIXING_STEPPERS];
#endif
unsigned short Stepper::acc_step_rate; // needed for deceleration start point
uint8_t Stepper::step_loops, Stepper::step_loops_nominal;
unsigned short Stepper::OCR1A_nominal;
volatile long Stepper::endstops_trigsteps[XYZ];
#if ENABLED(X_DUAL_STEPPER_DRIVERS)
#define X_APPLY_DIR(v,Q) do{ X_DIR_WRITE(v); X2_DIR_WRITE((v) != INVERT_X2_VS_X_DIR); }while(0)
#define X_APPLY_STEP(v,Q) do{ X_STEP_WRITE(v); X2_STEP_WRITE(v); }while(0)
#elif ENABLED(DUAL_X_CARRIAGE)
#define X_APPLY_DIR(v,ALWAYS) \
if (extruder_duplication_enabled || ALWAYS) { \
X_DIR_WRITE(v); \
X2_DIR_WRITE(v); \
} \
else { \
if (current_block->active_extruder) X2_DIR_WRITE(v); else X_DIR_WRITE(v); \
}
#define X_APPLY_STEP(v,ALWAYS) \
if (extruder_duplication_enabled || ALWAYS) { \
X_STEP_WRITE(v); \
X2_STEP_WRITE(v); \
} \
else { \
if (current_block->active_extruder != 0) X2_STEP_WRITE(v); else X_STEP_WRITE(v); \
}
#else
#define X_APPLY_DIR(v,Q) X_DIR_WRITE(v)
#define X_APPLY_STEP(v,Q) X_STEP_WRITE(v)
#endif
#if ENABLED(Y_DUAL_STEPPER_DRIVERS)
#define Y_APPLY_DIR(v,Q) do{ Y_DIR_WRITE(v); Y2_DIR_WRITE((v) != INVERT_Y2_VS_Y_DIR); }while(0)
#define Y_APPLY_STEP(v,Q) do{ Y_STEP_WRITE(v); Y2_STEP_WRITE(v); }while(0)
#else
#define Y_APPLY_DIR(v,Q) Y_DIR_WRITE(v)
#define Y_APPLY_STEP(v,Q) Y_STEP_WRITE(v)
#endif
#if ENABLED(Z_DUAL_STEPPER_DRIVERS)
#define Z_APPLY_DIR(v,Q) do{ Z_DIR_WRITE(v); Z2_DIR_WRITE(v); }while(0)
#if ENABLED(Z_DUAL_ENDSTOPS)
#define Z_APPLY_STEP(v,Q) \
if (performing_homing) { \
if (Z_HOME_DIR < 0) { \
if (!(TEST(endstops.old_endstop_bits, Z_MIN) && (count_direction[Z_AXIS] < 0)) && !locked_z_motor) Z_STEP_WRITE(v); \
if (!(TEST(endstops.old_endstop_bits, Z2_MIN) && (count_direction[Z_AXIS] < 0)) && !locked_z2_motor) Z2_STEP_WRITE(v); \
} \
else { \
if (!(TEST(endstops.old_endstop_bits, Z_MAX) && (count_direction[Z_AXIS] > 0)) && !locked_z_motor) Z_STEP_WRITE(v); \
if (!(TEST(endstops.old_endstop_bits, Z2_MAX) && (count_direction[Z_AXIS] > 0)) && !locked_z2_motor) Z2_STEP_WRITE(v); \
} \
} \
else { \
Z_STEP_WRITE(v); \
Z2_STEP_WRITE(v); \
}
#else
#define Z_APPLY_STEP(v,Q) do{ Z_STEP_WRITE(v); Z2_STEP_WRITE(v); }while(0)
#endif
#else
#define Z_APPLY_DIR(v,Q) Z_DIR_WRITE(v)
#define Z_APPLY_STEP(v,Q) Z_STEP_WRITE(v)
#endif
#if DISABLED(MIXING_EXTRUDER)
#define E_APPLY_STEP(v,Q) E_STEP_WRITE(v)
#endif
// intRes = longIn1 * longIn2 >> 24
// uses:
// r26 to store 0
// r27 to store bits 16-23 of the 48bit result. The top bit is used to round the two byte result.
// note that the lower two bytes and the upper byte of the 48bit result are not calculated.
// this can cause the result to be out by one as the lower bytes may cause carries into the upper ones.
// B0 A0 are bits 24-39 and are the returned value
// C1 B1 A1 is longIn1
// D2 C2 B2 A2 is longIn2
//
#define MultiU24X32toH16(intRes, longIn1, longIn2) \
asm volatile ( \
"clr r26 \n\t" \
"mul %A1, %B2 \n\t" \
"mov r27, r1 \n\t" \
"mul %B1, %C2 \n\t" \
"movw %A0, r0 \n\t" \
"mul %C1, %C2 \n\t" \
"add %B0, r0 \n\t" \
"mul %C1, %B2 \n\t" \
"add %A0, r0 \n\t" \
"adc %B0, r1 \n\t" \
"mul %A1, %C2 \n\t" \
"add r27, r0 \n\t" \
"adc %A0, r1 \n\t" \
"adc %B0, r26 \n\t" \
"mul %B1, %B2 \n\t" \
"add r27, r0 \n\t" \
"adc %A0, r1 \n\t" \
"adc %B0, r26 \n\t" \
"mul %C1, %A2 \n\t" \
"add r27, r0 \n\t" \
"adc %A0, r1 \n\t" \
"adc %B0, r26 \n\t" \
"mul %B1, %A2 \n\t" \
"add r27, r1 \n\t" \
"adc %A0, r26 \n\t" \
"adc %B0, r26 \n\t" \
"lsr r27 \n\t" \
"adc %A0, r26 \n\t" \
"adc %B0, r26 \n\t" \
"mul %D2, %A1 \n\t" \
"add %A0, r0 \n\t" \
"adc %B0, r1 \n\t" \
"mul %D2, %B1 \n\t" \
"add %B0, r0 \n\t" \
"clr r1 \n\t" \
: \
"=&r" (intRes) \
: \
"d" (longIn1), \
"d" (longIn2) \
: \
"r26" , "r27" \
)
// Some useful constants
#define ENABLE_STEPPER_DRIVER_INTERRUPT() SBI(TIMSK1, OCIE1A)
#define DISABLE_STEPPER_DRIVER_INTERRUPT() CBI(TIMSK1, OCIE1A)
/**
* __________________________
* /| |\ _________________ ^
* / | | \ /| |\ |
* / | | \ / | | \ s
* / | | | | | \ p
* / | | | | | \ e
* +-----+------------------------+---+--+---------------+----+ e
* | BLOCK 1 | BLOCK 2 | d
*
* time ----->
*
* The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates
* first block->accelerate_until step_events_completed, then keeps going at constant speed until
* step_events_completed reaches block->decelerate_after after which it decelerates until the trapezoid generator is reset.
* The slope of acceleration is calculated using v = u + at where t is the accumulated timer values of the steps so far.
*/
void Stepper::wake_up() {
// TCNT1 = 0;
ENABLE_STEPPER_DRIVER_INTERRUPT();
}
/**
* Set the stepper direction of each axis
*
* COREXY: X_AXIS=A_AXIS and Y_AXIS=B_AXIS
* COREXZ: X_AXIS=A_AXIS and Z_AXIS=C_AXIS
* COREYZ: Y_AXIS=B_AXIS and Z_AXIS=C_AXIS
*/
void Stepper::set_directions() {
#define SET_STEP_DIR(AXIS) \
if (motor_direction(AXIS ##_AXIS)) { \
AXIS ##_APPLY_DIR(INVERT_## AXIS ##_DIR, false); \
count_direction[AXIS ##_AXIS] = -1; \
} \
else { \
AXIS ##_APPLY_DIR(!INVERT_## AXIS ##_DIR, false); \
count_direction[AXIS ##_AXIS] = 1; \
}
#if HAS_X_DIR
SET_STEP_DIR(X); // A
#endif
#if HAS_Y_DIR
SET_STEP_DIR(Y); // B
#endif
#if HAS_Z_DIR
SET_STEP_DIR(Z); // C
#endif
#if DISABLED(ADVANCE) && DISABLED(LIN_ADVANCE)
if (motor_direction(E_AXIS)) {
REV_E_DIR();
count_direction[E_AXIS] = -1;
}
else {
NORM_E_DIR();
count_direction[E_AXIS] = 1;
}
#endif // !ADVANCE && !LIN_ADVANCE
}
#if ENABLED(ENDSTOP_INTERRUPTS_FEATURE)
extern volatile uint8_t e_hit;
#endif
/**
* Stepper Driver Interrupt
*
* Directly pulses the stepper motors at high frequency.
* Timer 1 runs at a base frequency of 2MHz, with this ISR using OCR1A compare mode.
*
* OCR1A Frequency
* 1 2 MHz
* 50 40 KHz
* 100 20 KHz - capped max rate
* 200 10 KHz - nominal max rate
* 2000 1 KHz - sleep rate
* 4000 500 Hz - init rate
*/
ISR(TIMER1_COMPA_vect) { Stepper::isr(); }
void Stepper::isr() {
if (cleaning_buffer_counter) {
current_block = NULL;
planner.discard_current_block();
#ifdef SD_FINISHED_RELEASECOMMAND
if ((cleaning_buffer_counter == 1) && (SD_FINISHED_STEPPERRELEASE)) enqueue_and_echo_commands_P(PSTR(SD_FINISHED_RELEASECOMMAND));
#endif
cleaning_buffer_counter--;
OCR1A = 200; // Run at max speed - 10 KHz
return;
}
// If there is no current block, attempt to pop one from the buffer
if (!current_block) {
// Anything in the buffer?
current_block = planner.get_current_block();
if (current_block) {
trapezoid_generator_reset();
// Initialize Bresenham counters to 1/2 the ceiling
counter_X = counter_Y = counter_Z = counter_E = -(current_block->step_event_count >> 1);
#if ENABLED(MIXING_EXTRUDER)
MIXING_STEPPERS_LOOP(i)
counter_m[i] = -(current_block->mix_event_count[i] >> 1);
#endif
step_events_completed = 0;
#if ENABLED(Z_LATE_ENABLE)
if (current_block->steps[Z_AXIS] > 0) {
enable_z();
OCR1A = 2000; // Run at slow speed - 1 KHz
return;
}
#endif
// #if ENABLED(ADVANCE)
// e_steps[TOOL_E_INDEX] = 0;
// #endif
}
else {
OCR1A = 2000; // Run at slow speed - 1 KHz
return;
}
}
// Update endstops state, if enabled
if (endstops.enabled
#if HAS_BED_PROBE
|| endstops.z_probe_enabled
#endif
)
#if ENABLED(ENDSTOP_INTERRUPTS_FEATURE)
if(e_hit) {
#endif
endstops.update();
#if ENABLED(ENDSTOP_INTERRUPTS_FEATURE)
e_hit--;
}
#endif
// Take multiple steps per interrupt (For high speed moves)
bool all_steps_done = false;
for (int8_t i = 0; i < step_loops; i++) {
#ifndef USBCON
customizedSerial.checkRx(); // Check for serial chars.
#endif
#if ENABLED(LIN_ADVANCE)
counter_E += current_block->steps[E_AXIS];
if (counter_E > 0) {
counter_E -= current_block->step_event_count;
#if DISABLED(MIXING_EXTRUDER)
// Don't step E here for mixing extruder
count_position[E_AXIS] += count_direction[E_AXIS];
motor_direction(E_AXIS) ? --e_steps[TOOL_E_INDEX] : ++e_steps[TOOL_E_INDEX];
#endif
}
#if ENABLED(MIXING_EXTRUDER)
// Step mixing steppers proportionally
const bool dir = motor_direction(E_AXIS);
MIXING_STEPPERS_LOOP(j) {
counter_m[j] += current_block->steps[E_AXIS];
if (counter_m[j] > 0) {
counter_m[j] -= current_block->mix_event_count[j];
dir ? --e_steps[j] : ++e_steps[j];
}
}
#endif
#elif ENABLED(ADVANCE)
// Always count the unified E axis
counter_E += current_block->steps[E_AXIS];
if (counter_E > 0) {
counter_E -= current_block->step_event_count;
#if DISABLED(MIXING_EXTRUDER)
// Don't step E here for mixing extruder
motor_direction(E_AXIS) ? --e_steps[TOOL_E_INDEX] : ++e_steps[TOOL_E_INDEX];
#endif
}
#if ENABLED(MIXING_EXTRUDER)
// Step mixing steppers proportionally
const bool dir = motor_direction(E_AXIS);
MIXING_STEPPERS_LOOP(j) {
counter_m[j] += current_block->steps[E_AXIS];
if (counter_m[j] > 0) {
counter_m[j] -= current_block->mix_event_count[j];
dir ? --e_steps[j] : ++e_steps[j];
}
}
#endif // MIXING_EXTRUDER
#endif // ADVANCE or LIN_ADVANCE
#define _COUNTER(AXIS) counter_## AXIS
#define _APPLY_STEP(AXIS) AXIS ##_APPLY_STEP
#define _INVERT_STEP_PIN(AXIS) INVERT_## AXIS ##_STEP_PIN
// Advance the Bresenham counter; start a pulse if the axis needs a step
#define PULSE_START(AXIS) \
_COUNTER(AXIS) += current_block->steps[_AXIS(AXIS)]; \
if (_COUNTER(AXIS) > 0) { _APPLY_STEP(AXIS)(!_INVERT_STEP_PIN(AXIS),0); }
// Stop an active pulse, reset the Bresenham counter, update the position
#define PULSE_STOP(AXIS) \
if (_COUNTER(AXIS) > 0) { \
_COUNTER(AXIS) -= current_block->step_event_count; \
count_position[_AXIS(AXIS)] += count_direction[_AXIS(AXIS)]; \
_APPLY_STEP(AXIS)(_INVERT_STEP_PIN(AXIS),0); \
}
#define CYCLES_EATEN_BY_CODE 240
// If a minimum pulse time was specified get the CPU clock
#if STEP_PULSE_CYCLES > CYCLES_EATEN_BY_CODE
static uint32_t pulse_start;
pulse_start = TCNT0;
#endif
#if HAS_X_STEP
PULSE_START(X);
#endif
#if HAS_Y_STEP
PULSE_START(Y);
#endif
#if HAS_Z_STEP
PULSE_START(Z);
#endif
// For non-advance use linear interpolation for E also
#if DISABLED(ADVANCE) && DISABLED(LIN_ADVANCE)
#if ENABLED(MIXING_EXTRUDER)
// Keep updating the single E axis
counter_E += current_block->steps[E_AXIS];
// Tick the counters used for this mix
MIXING_STEPPERS_LOOP(j) {
// Step mixing steppers (proportionally)
counter_m[j] += current_block->steps[E_AXIS];
// Step when the counter goes over zero
if (counter_m[j] > 0) En_STEP_WRITE(j, !INVERT_E_STEP_PIN);
}
#else // !MIXING_EXTRUDER
PULSE_START(E);
#endif
#endif // !ADVANCE && !LIN_ADVANCE
// For a minimum pulse time wait before stopping pulses
#if STEP_PULSE_CYCLES > CYCLES_EATEN_BY_CODE
while ((uint32_t)(TCNT0 - pulse_start) < STEP_PULSE_CYCLES - CYCLES_EATEN_BY_CODE) { /* nada */ }
#endif
#if HAS_X_STEP
PULSE_STOP(X);
#endif
#if HAS_Y_STEP
PULSE_STOP(Y);
#endif
#if HAS_Z_STEP
PULSE_STOP(Z);
#endif
#if DISABLED(ADVANCE) && DISABLED(LIN_ADVANCE)
#if ENABLED(MIXING_EXTRUDER)
// Always step the single E axis
if (counter_E > 0) {
counter_E -= current_block->step_event_count;
count_position[E_AXIS] += count_direction[E_AXIS];
}
MIXING_STEPPERS_LOOP(j) {
if (counter_m[j] > 0) {
counter_m[j] -= current_block->mix_event_count[j];
En_STEP_WRITE(j, INVERT_E_STEP_PIN);
}
}
#else // !MIXING_EXTRUDER
PULSE_STOP(E);
#endif
#endif // !ADVANCE && !LIN_ADVANCE
if (++step_events_completed >= current_block->step_event_count) {
all_steps_done = true;
break;
}
}
#if ENABLED(LIN_ADVANCE)
if (current_block->use_advance_lead) {
int delta_adv_steps = current_estep_rate[TOOL_E_INDEX] - current_adv_steps[TOOL_E_INDEX];
current_adv_steps[TOOL_E_INDEX] += delta_adv_steps;
#if ENABLED(MIXING_EXTRUDER)
// Mixing extruders apply advance lead proportionally
MIXING_STEPPERS_LOOP(j)
e_steps[j] += delta_adv_steps * current_block->step_event_count / current_block->mix_event_count[j];
#else
// For most extruders, advance the single E stepper
e_steps[TOOL_E_INDEX] += delta_adv_steps;
#endif
}
#endif
#if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
// If we have esteps to execute, fire the next advance_isr "now"
if (e_steps[TOOL_E_INDEX]) OCR0A = TCNT0 + 2;
#endif
// Calculate new timer value
uint16_t timer, step_rate;
if (step_events_completed <= (uint32_t)current_block->accelerate_until) {
MultiU24X32toH16(acc_step_rate, acceleration_time, current_block->acceleration_rate);
acc_step_rate += current_block->initial_rate;
// upper limit
NOMORE(acc_step_rate, current_block->nominal_rate);
// step_rate to timer interval
timer = calc_timer(acc_step_rate);
OCR1A = timer;
acceleration_time += timer;
#if ENABLED(LIN_ADVANCE)
if (current_block->use_advance_lead) {
#if ENABLED(MIXING_EXTRUDER)
MIXING_STEPPERS_LOOP(j)
current_estep_rate[j] = ((uint32_t)acc_step_rate * current_block->abs_adv_steps_multiplier8 * current_block->step_event_count / current_block->mix_event_count[j]) >> 17;
#else
current_estep_rate[TOOL_E_INDEX] = ((uint32_t)acc_step_rate * current_block->abs_adv_steps_multiplier8) >> 17;
#endif
}
#elif ENABLED(ADVANCE)
advance += advance_rate * step_loops;
//NOLESS(advance, current_block->advance);
long advance_whole = advance >> 8,
advance_factor = advance_whole - old_advance;
// Do E steps + advance steps
#if ENABLED(MIXING_EXTRUDER)
// ...for mixing steppers proportionally
MIXING_STEPPERS_LOOP(j)
e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
#else
// ...for the active extruder
e_steps[TOOL_E_INDEX] += advance_factor;
#endif
old_advance = advance_whole;
#endif // ADVANCE or LIN_ADVANCE
#if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
eISR_Rate = (timer >> 3) * step_loops / abs(e_steps[TOOL_E_INDEX]); //>> 3 is divide by 8. Reason: Timer 1 runs at 16/8=2MHz, Timer 0 at 16/64=0.25MHz. ==> 2/0.25=8.
#endif
}
else if (step_events_completed > (uint32_t)current_block->decelerate_after) {
MultiU24X32toH16(step_rate, deceleration_time, current_block->acceleration_rate);
if (step_rate < acc_step_rate) { // Still decelerating?
step_rate = acc_step_rate - step_rate;
NOLESS(step_rate, current_block->final_rate);
}
else
step_rate = current_block->final_rate;
// step_rate to timer interval
timer = calc_timer(step_rate);
OCR1A = timer;
deceleration_time += timer;
#if ENABLED(LIN_ADVANCE)
if (current_block->use_advance_lead) {
#if ENABLED(MIXING_EXTRUDER)
MIXING_STEPPERS_LOOP(j)
current_estep_rate[j] = ((uint32_t)step_rate * current_block->abs_adv_steps_multiplier8 * current_block->step_event_count / current_block->mix_event_count[j]) >> 17;
#else
current_estep_rate[TOOL_E_INDEX] = ((uint32_t)step_rate * current_block->abs_adv_steps_multiplier8) >> 17;
#endif
}
#elif ENABLED(ADVANCE)
advance -= advance_rate * step_loops;
NOLESS(advance, final_advance);
// Do E steps + advance steps
long advance_whole = advance >> 8,
advance_factor = advance_whole - old_advance;
#if ENABLED(MIXING_EXTRUDER)
MIXING_STEPPERS_LOOP(j)
e_steps[j] += advance_factor * current_block->step_event_count / current_block->mix_event_count[j];
#else
e_steps[TOOL_E_INDEX] += advance_factor;
#endif
old_advance = advance_whole;
#endif // ADVANCE or LIN_ADVANCE
#if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
eISR_Rate = (timer >> 3) * step_loops / abs(e_steps[TOOL_E_INDEX]);
#endif
}
else {
#if ENABLED(LIN_ADVANCE)
if (current_block->use_advance_lead)
current_estep_rate[TOOL_E_INDEX] = final_estep_rate;
eISR_Rate = (OCR1A_nominal >> 3) * step_loops_nominal / abs(e_steps[TOOL_E_INDEX]);
#endif
OCR1A = OCR1A_nominal;
// ensure we're running at the correct step rate, even if we just came off an acceleration
step_loops = step_loops_nominal;
}
NOLESS(OCR1A, TCNT1 + 16);
// If current block is finished, reset pointer
if (all_steps_done) {
current_block = NULL;
planner.discard_current_block();
}
}
#if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
// Timer interrupt for E. e_steps is set in the main routine;
// Timer 0 is shared with millies
ISR(TIMER0_COMPA_vect) { Stepper::advance_isr(); }
void Stepper::advance_isr() {
old_OCR0A += eISR_Rate;
OCR0A = old_OCR0A;
#define SET_E_STEP_DIR(INDEX) \
if (e_steps[INDEX]) E## INDEX ##_DIR_WRITE(e_steps[INDEX] < 0 ? INVERT_E## INDEX ##_DIR : !INVERT_E## INDEX ##_DIR)
#define START_E_PULSE(INDEX) \
if (e_steps[INDEX]) E## INDEX ##_STEP_WRITE(!INVERT_E_STEP_PIN)
#define STOP_E_PULSE(INDEX) \
if (e_steps[INDEX]) { \
e_steps[INDEX] < 0 ? ++e_steps[INDEX] : --e_steps[INDEX]; \
E## INDEX ##_STEP_WRITE(INVERT_E_STEP_PIN); \
}
SET_E_STEP_DIR(0);
#if E_STEPPERS > 1
SET_E_STEP_DIR(1);
#if E_STEPPERS > 2
SET_E_STEP_DIR(2);
#if E_STEPPERS > 3
SET_E_STEP_DIR(3);
#endif
#endif
#endif
#define CYCLES_EATEN_BY_E 60
// Step all E steppers that have steps
for (uint8_t i = 0; i < step_loops; i++) {
#if STEP_PULSE_CYCLES > CYCLES_EATEN_BY_E
static uint32_t pulse_start;
pulse_start = TCNT0;
#endif
START_E_PULSE(0);
#if E_STEPPERS > 1
START_E_PULSE(1);
#if E_STEPPERS > 2
START_E_PULSE(2);
#if E_STEPPERS > 3
START_E_PULSE(3);
#endif
#endif
#endif
// For a minimum pulse time wait before stopping pulses
#if STEP_PULSE_CYCLES > CYCLES_EATEN_BY_E
while ((uint32_t)(TCNT0 - pulse_start) < STEP_PULSE_CYCLES - CYCLES_EATEN_BY_E) { /* nada */ }
#endif
STOP_E_PULSE(0);
#if E_STEPPERS > 1
STOP_E_PULSE(1);
#if E_STEPPERS > 2
STOP_E_PULSE(2);
#if E_STEPPERS > 3
STOP_E_PULSE(3);
#endif
#endif
#endif
}
}
#endif // ADVANCE or LIN_ADVANCE
void Stepper::init() {
// Init Digipot Motor Current
#if HAS_DIGIPOTSS || HAS_MOTOR_CURRENT_PWM
digipot_init();
#endif
// Init Microstepping Pins
#if HAS_MICROSTEPS
microstep_init();
#endif
// Init TMC Steppers
#if ENABLED(HAVE_TMCDRIVER)
tmc_init();
#endif
// Init L6470 Steppers
#if ENABLED(HAVE_L6470DRIVER)
L6470_init();
#endif
// Init Dir Pins
#if HAS_X_DIR
X_DIR_INIT;
#endif
#if HAS_X2_DIR
X2_DIR_INIT;
#endif
#if HAS_Y_DIR
Y_DIR_INIT;
#if ENABLED(Y_DUAL_STEPPER_DRIVERS) && HAS_Y2_DIR
Y2_DIR_INIT;
#endif
#endif
#if HAS_Z_DIR
Z_DIR_INIT;
#if ENABLED(Z_DUAL_STEPPER_DRIVERS) && HAS_Z2_DIR
Z2_DIR_INIT;
#endif
#endif
#if HAS_E0_DIR
E0_DIR_INIT;
#endif
#if HAS_E1_DIR
E1_DIR_INIT;
#endif
#if HAS_E2_DIR
E2_DIR_INIT;
#endif
#if HAS_E3_DIR
E3_DIR_INIT;
#endif
// Init Enable Pins - steppers default to disabled.
#if HAS_X_ENABLE
X_ENABLE_INIT;
if (!X_ENABLE_ON) X_ENABLE_WRITE(HIGH);
#if ENABLED(DUAL_X_CARRIAGE) && HAS_X2_ENABLE
X2_ENABLE_INIT;
if (!X_ENABLE_ON) X2_ENABLE_WRITE(HIGH);
#endif
#endif
#if HAS_Y_ENABLE
Y_ENABLE_INIT;
if (!Y_ENABLE_ON) Y_ENABLE_WRITE(HIGH);
#if ENABLED(Y_DUAL_STEPPER_DRIVERS) && HAS_Y2_ENABLE
Y2_ENABLE_INIT;
if (!Y_ENABLE_ON) Y2_ENABLE_WRITE(HIGH);
#endif
#endif
#if HAS_Z_ENABLE
Z_ENABLE_INIT;
if (!Z_ENABLE_ON) Z_ENABLE_WRITE(HIGH);
#if ENABLED(Z_DUAL_STEPPER_DRIVERS) && HAS_Z2_ENABLE
Z2_ENABLE_INIT;
if (!Z_ENABLE_ON) Z2_ENABLE_WRITE(HIGH);
#endif
#endif
#if HAS_E0_ENABLE
E0_ENABLE_INIT;
if (!E_ENABLE_ON) E0_ENABLE_WRITE(HIGH);
#endif
#if HAS_E1_ENABLE
E1_ENABLE_INIT;
if (!E_ENABLE_ON) E1_ENABLE_WRITE(HIGH);
#endif
#if HAS_E2_ENABLE
E2_ENABLE_INIT;
if (!E_ENABLE_ON) E2_ENABLE_WRITE(HIGH);
#endif
#if HAS_E3_ENABLE
E3_ENABLE_INIT;
if (!E_ENABLE_ON) E3_ENABLE_WRITE(HIGH);
#endif
// Init endstops and pullups
endstops.init();
#define _STEP_INIT(AXIS) AXIS ##_STEP_INIT
#define _WRITE_STEP(AXIS, HIGHLOW) AXIS ##_STEP_WRITE(HIGHLOW)
#define _DISABLE(axis) disable_## axis()
#define AXIS_INIT(axis, AXIS, PIN) \
_STEP_INIT(AXIS); \
_WRITE_STEP(AXIS, _INVERT_STEP_PIN(PIN)); \
_DISABLE(axis)
#define E_AXIS_INIT(NUM) AXIS_INIT(e## NUM, E## NUM, E)
// Init Step Pins
#if HAS_X_STEP
#if ENABLED(X_DUAL_STEPPER_DRIVERS) || ENABLED(DUAL_X_CARRIAGE)
X2_STEP_INIT;
X2_STEP_WRITE(INVERT_X_STEP_PIN);
#endif
AXIS_INIT(x, X, X);
#endif
#if HAS_Y_STEP
#if ENABLED(Y_DUAL_STEPPER_DRIVERS)
Y2_STEP_INIT;
Y2_STEP_WRITE(INVERT_Y_STEP_PIN);
#endif
AXIS_INIT(y, Y, Y);
#endif
#if HAS_Z_STEP
#if ENABLED(Z_DUAL_STEPPER_DRIVERS)
Z2_STEP_INIT;
Z2_STEP_WRITE(INVERT_Z_STEP_PIN);
#endif
AXIS_INIT(z, Z, Z);
#endif
#if HAS_E0_STEP
E_AXIS_INIT(0);
#endif
#if HAS_E1_STEP
E_AXIS_INIT(1);
#endif
#if HAS_E2_STEP
E_AXIS_INIT(2);
#endif
#if HAS_E3_STEP
E_AXIS_INIT(3);
#endif
// waveform generation = 0100 = CTC
CBI(TCCR1B, WGM13);
SBI(TCCR1B, WGM12);
CBI(TCCR1A, WGM11);
CBI(TCCR1A, WGM10);
// output mode = 00 (disconnected)
TCCR1A &= ~(3 << COM1A0);
TCCR1A &= ~(3 << COM1B0);
// Set the timer pre-scaler
// Generally we use a divider of 8, resulting in a 2MHz timer
// frequency on a 16MHz MCU. If you are going to change this, be
// sure to regenerate speed_lookuptable.h with
// create_speed_lookuptable.py
TCCR1B = (TCCR1B & ~(0x07 << CS10)) | (2 << CS10);
// Init Stepper ISR to 122 Hz for quick starting
OCR1A = 0x4000;
TCNT1 = 0;
ENABLE_STEPPER_DRIVER_INTERRUPT();
#if ENABLED(ADVANCE) || ENABLED(LIN_ADVANCE)
for (int i = 0; i < E_STEPPERS; i++) {
e_steps[i] = 0;
#if ENABLED(LIN_ADVANCE)
current_adv_steps[i] = 0;
#endif
}
#if defined(TCCR0A) && defined(WGM01)
CBI(TCCR0A, WGM01);
CBI(TCCR0A, WGM00);
#endif
SBI(TIMSK0, OCIE0A);
#endif // ADVANCE or LIN_ADVANCE
endstops.enable(true); // Start with endstops active. After homing they can be disabled
sei();
set_directions(); // Init directions to last_direction_bits = 0
}
/**
* Block until all buffered steps are executed
*/
void Stepper::synchronize() { while (planner.blocks_queued()) idle(); }
/**
* Set the stepper positions directly in steps
*
* The input is based on the typical per-axis XYZ steps.
* For CORE machines XYZ needs to be translated to ABC.
*
* This allows get_axis_position_mm to correctly
* derive the current XYZ position later on.
*/
void Stepper::set_position(const long &a, const long &b, const long &c, const long &e) {
synchronize(); // Bad to set stepper counts in the middle of a move
CRITICAL_SECTION_START;
#if ENABLED(COREXY)
// corexy positioning
// these equations follow the form of the dA and dB equations on http://www.corexy.com/theory.html
count_position[A_AXIS] = a + b;
count_position[B_AXIS] = a - b;
count_position[Z_AXIS] = c;
#elif ENABLED(COREXZ)
// corexz planning
count_position[A_AXIS] = a + c;
count_position[Y_AXIS] = b;
count_position[C_AXIS] = a - c;
#elif ENABLED(COREYZ)
// coreyz planning
count_position[X_AXIS] = a;
count_position[B_AXIS] = b + c;
count_position[C_AXIS] = b - c;
#else
// default non-h-bot planning
count_position[X_AXIS] = a;
count_position[Y_AXIS] = b;
count_position[Z_AXIS] = c;
#endif
count_position[E_AXIS] = e;
CRITICAL_SECTION_END;
}
void Stepper::set_position(const AxisEnum &axis, const long &v) {
CRITICAL_SECTION_START;
count_position[axis] = v;
CRITICAL_SECTION_END;
}
void Stepper::set_e_position(const long &e) {
CRITICAL_SECTION_START;
count_position[E_AXIS] = e;
CRITICAL_SECTION_END;
}
/**
* Get a stepper's position in steps.
*/
long Stepper::position(AxisEnum axis) {
CRITICAL_SECTION_START;
long count_pos = count_position[axis];
CRITICAL_SECTION_END;
return count_pos;
}
/**
* Get an axis position according to stepper position(s)
* For CORE machines apply translation from ABC to XYZ.
*/
float Stepper::get_axis_position_mm(AxisEnum axis) {
float axis_steps;
#if ENABLED(COREXY) || ENABLED(COREXZ) || ENABLED(COREYZ)
// Requesting one of the "core" axes?
if (axis == CORE_AXIS_1 || axis == CORE_AXIS_2) {
CRITICAL_SECTION_START;
long pos1 = count_position[CORE_AXIS_1],
pos2 = count_position[CORE_AXIS_2];
CRITICAL_SECTION_END;
// ((a1+a2)+(a1-a2))/2 -> (a1+a2+a1-a2)/2 -> (a1+a1)/2 -> a1
// ((a1+a2)-(a1-a2))/2 -> (a1+a2-a1+a2)/2 -> (a2+a2)/2 -> a2
axis_steps = (pos1 + ((axis == CORE_AXIS_1) ? pos2 : -pos2)) * 0.5f;
}
else
axis_steps = position(axis);
#else
axis_steps = position(axis);
#endif
return axis_steps * planner.steps_to_mm[axis];
}
void Stepper::finish_and_disable() {
synchronize();
disable_all_steppers();
}
void Stepper::quick_stop() {
cleaning_buffer_counter = 5000;
DISABLE_STEPPER_DRIVER_INTERRUPT();
while (planner.blocks_queued()) planner.discard_current_block();
current_block = NULL;
ENABLE_STEPPER_DRIVER_INTERRUPT();
}
void Stepper::endstop_triggered(AxisEnum axis) {
#if ENABLED(COREXY) || ENABLED(COREXZ) || ENABLED(COREYZ)
float axis_pos = count_position[axis];
if (axis == CORE_AXIS_1)
axis_pos = (axis_pos + count_position[CORE_AXIS_2]) * 0.5;
else if (axis == CORE_AXIS_2)
axis_pos = (count_position[CORE_AXIS_1] - axis_pos) * 0.5;
endstops_trigsteps[axis] = axis_pos;
#else // !COREXY && !COREXZ && !COREYZ
endstops_trigsteps[axis] = count_position[axis];
#endif // !COREXY && !COREXZ && !COREYZ
kill_current_block();
}
void Stepper::report_positions() {
CRITICAL_SECTION_START;
long xpos = count_position[X_AXIS],
ypos = count_position[Y_AXIS],
zpos = count_position[Z_AXIS];
CRITICAL_SECTION_END;
#if ENABLED(COREXY) || ENABLED(COREXZ) || IS_SCARA
SERIAL_PROTOCOLPGM(MSG_COUNT_A);
#else
SERIAL_PROTOCOLPGM(MSG_COUNT_X);
#endif
SERIAL_PROTOCOL(xpos);
#if ENABLED(COREXY) || ENABLED(COREYZ) || IS_SCARA
SERIAL_PROTOCOLPGM(" B:");
#else
SERIAL_PROTOCOLPGM(" Y:");
#endif
SERIAL_PROTOCOL(ypos);
#if ENABLED(COREXZ) || ENABLED(COREYZ)
SERIAL_PROTOCOLPGM(" C:");
#else
SERIAL_PROTOCOLPGM(" Z:");
#endif
SERIAL_PROTOCOL(zpos);
SERIAL_EOL;
}
#if ENABLED(BABYSTEPPING)
#define _ENABLE(axis) enable_## axis()
#define _READ_DIR(AXIS) AXIS ##_DIR_READ
#define _INVERT_DIR(AXIS) INVERT_## AXIS ##_DIR
#define _APPLY_DIR(AXIS, INVERT) AXIS ##_APPLY_DIR(INVERT, true)
#define BABYSTEP_AXIS(axis, AXIS, INVERT) { \
_ENABLE(axis); \
uint8_t old_pin = _READ_DIR(AXIS); \
_APPLY_DIR(AXIS, _INVERT_DIR(AXIS)^direction^INVERT); \
_APPLY_STEP(AXIS)(!_INVERT_STEP_PIN(AXIS), true); \
delayMicroseconds(2); \
_APPLY_STEP(AXIS)(_INVERT_STEP_PIN(AXIS), true); \
_APPLY_DIR(AXIS, old_pin); \
}
// MUST ONLY BE CALLED BY AN ISR,
// No other ISR should ever interrupt this!
void Stepper::babystep(const AxisEnum axis, const bool direction) {
switch (axis) {
case X_AXIS:
BABYSTEP_AXIS(x, X, false);
break;
case Y_AXIS:
BABYSTEP_AXIS(y, Y, false);
break;
case Z_AXIS: {
#if DISABLED(DELTA)
BABYSTEP_AXIS(z, Z, BABYSTEP_INVERT_Z);
#else // DELTA
bool z_direction = direction ^ BABYSTEP_INVERT_Z;
enable_x();
enable_y();
enable_z();
uint8_t old_x_dir_pin = X_DIR_READ,
old_y_dir_pin = Y_DIR_READ,
old_z_dir_pin = Z_DIR_READ;
//setup new step
X_DIR_WRITE(INVERT_X_DIR ^ z_direction);
Y_DIR_WRITE(INVERT_Y_DIR ^ z_direction);
Z_DIR_WRITE(INVERT_Z_DIR ^ z_direction);
//perform step
X_STEP_WRITE(!INVERT_X_STEP_PIN);
Y_STEP_WRITE(!INVERT_Y_STEP_PIN);
Z_STEP_WRITE(!INVERT_Z_STEP_PIN);
delayMicroseconds(2);
X_STEP_WRITE(INVERT_X_STEP_PIN);
Y_STEP_WRITE(INVERT_Y_STEP_PIN);
Z_STEP_WRITE(INVERT_Z_STEP_PIN);
//get old pin state back.
X_DIR_WRITE(old_x_dir_pin);
Y_DIR_WRITE(old_y_dir_pin);
Z_DIR_WRITE(old_z_dir_pin);
#endif
} break;
default: break;
}
}
#endif //BABYSTEPPING
/**
* Software-controlled Stepper Motor Current
*/
#if HAS_DIGIPOTSS
// From Arduino DigitalPotControl example
void Stepper::digitalPotWrite(int address, int value) {
WRITE(DIGIPOTSS_PIN, LOW); // take the SS pin low to select the chip
SPI.transfer(address); // send in the address and value via SPI:
SPI.transfer(value);
WRITE(DIGIPOTSS_PIN, HIGH); // take the SS pin high to de-select the chip:
//delay(10);
}
#endif //HAS_DIGIPOTSS
#if HAS_DIGIPOTSS || HAS_MOTOR_CURRENT_PWM
void Stepper::digipot_init() {
#if HAS_DIGIPOTSS
static const uint8_t digipot_motor_current[] = DIGIPOT_MOTOR_CURRENT;
SPI.begin();
SET_OUTPUT(DIGIPOTSS_PIN);
for (uint8_t i = 0; i < COUNT(digipot_motor_current); i++) {
//digitalPotWrite(digipot_ch[i], digipot_motor_current[i]);
digipot_current(i, digipot_motor_current[i]);
}
#elif HAS_MOTOR_CURRENT_PWM
#if PIN_EXISTS(MOTOR_CURRENT_PWM_XY)
SET_OUTPUT(MOTOR_CURRENT_PWM_XY_PIN);
digipot_current(0, motor_current_setting[0]);
#endif
#if PIN_EXISTS(MOTOR_CURRENT_PWM_Z)
SET_OUTPUT(MOTOR_CURRENT_PWM_Z_PIN);
digipot_current(1, motor_current_setting[1]);
#endif
#if PIN_EXISTS(MOTOR_CURRENT_PWM_E)
SET_OUTPUT(MOTOR_CURRENT_PWM_E_PIN);
digipot_current(2, motor_current_setting[2]);
#endif
//Set timer5 to 31khz so the PWM of the motor power is as constant as possible. (removes a buzzing noise)
TCCR5B = (TCCR5B & ~(_BV(CS50) | _BV(CS51) | _BV(CS52))) | _BV(CS50);
#endif
}
void Stepper::digipot_current(uint8_t driver, int current) {
#if HAS_DIGIPOTSS
const uint8_t digipot_ch[] = DIGIPOT_CHANNELS;
digitalPotWrite(digipot_ch[driver], current);
#elif HAS_MOTOR_CURRENT_PWM
#define _WRITE_CURRENT_PWM(P) analogWrite(P, 255L * current / (MOTOR_CURRENT_PWM_RANGE))
switch (driver) {
#if PIN_EXISTS(MOTOR_CURRENT_PWM_XY)
case 0: _WRITE_CURRENT_PWM(MOTOR_CURRENT_PWM_XY_PIN); break;
#endif
#if PIN_EXISTS(MOTOR_CURRENT_PWM_Z)
case 1: _WRITE_CURRENT_PWM(MOTOR_CURRENT_PWM_Z_PIN); break;
#endif
#if PIN_EXISTS(MOTOR_CURRENT_PWM_E)
case 2: _WRITE_CURRENT_PWM(MOTOR_CURRENT_PWM_E_PIN); break;
#endif
}
#endif
}
#endif
#if HAS_MICROSTEPS
/**
* Software-controlled Microstepping
*/
void Stepper::microstep_init() {
SET_OUTPUT(X_MS1_PIN);
SET_OUTPUT(X_MS2_PIN);
#if HAS_MICROSTEPS_Y
SET_OUTPUT(Y_MS1_PIN);
SET_OUTPUT(Y_MS2_PIN);
#endif
#if HAS_MICROSTEPS_Z
SET_OUTPUT(Z_MS1_PIN);
SET_OUTPUT(Z_MS2_PIN);
#endif
#if HAS_MICROSTEPS_E0
SET_OUTPUT(E0_MS1_PIN);
SET_OUTPUT(E0_MS2_PIN);
#endif
#if HAS_MICROSTEPS_E1
SET_OUTPUT(E1_MS1_PIN);
SET_OUTPUT(E1_MS2_PIN);
#endif
static const uint8_t microstep_modes[] = MICROSTEP_MODES;
for (uint16_t i = 0; i < COUNT(microstep_modes); i++)
microstep_mode(i, microstep_modes[i]);
}
void Stepper::microstep_ms(uint8_t driver, int8_t ms1, int8_t ms2) {
if (ms1 >= 0) switch (driver) {
case 0: digitalWrite(X_MS1_PIN, ms1); break;
#if HAS_MICROSTEPS_Y
case 1: digitalWrite(Y_MS1_PIN, ms1); break;
#endif
#if HAS_MICROSTEPS_Z
case 2: digitalWrite(Z_MS1_PIN, ms1); break;
#endif
#if HAS_MICROSTEPS_E0
case 3: digitalWrite(E0_MS1_PIN, ms1); break;
#endif
#if HAS_MICROSTEPS_E1
case 4: digitalWrite(E1_MS1_PIN, ms1); break;
#endif
}
if (ms2 >= 0) switch (driver) {
case 0: digitalWrite(X_MS2_PIN, ms2); break;
#if HAS_MICROSTEPS_Y
case 1: digitalWrite(Y_MS2_PIN, ms2); break;
#endif
#if HAS_MICROSTEPS_Z
case 2: digitalWrite(Z_MS2_PIN, ms2); break;
#endif
#if HAS_MICROSTEPS_E0
case 3: digitalWrite(E0_MS2_PIN, ms2); break;
#endif
#if HAS_MICROSTEPS_E1
case 4: digitalWrite(E1_MS2_PIN, ms2); break;
#endif
}
}
void Stepper::microstep_mode(uint8_t driver, uint8_t stepping_mode) {
switch (stepping_mode) {
case 1: microstep_ms(driver, MICROSTEP1); break;
case 2: microstep_ms(driver, MICROSTEP2); break;
case 4: microstep_ms(driver, MICROSTEP4); break;
case 8: microstep_ms(driver, MICROSTEP8); break;
case 16: microstep_ms(driver, MICROSTEP16); break;
}
}
void Stepper::microstep_readings() {
SERIAL_PROTOCOLLNPGM("MS1,MS2 Pins");
SERIAL_PROTOCOLPGM("X: ");
SERIAL_PROTOCOL(READ(X_MS1_PIN));
SERIAL_PROTOCOLLN(READ(X_MS2_PIN));
#if HAS_MICROSTEPS_Y
SERIAL_PROTOCOLPGM("Y: ");
SERIAL_PROTOCOL(READ(Y_MS1_PIN));
SERIAL_PROTOCOLLN(READ(Y_MS2_PIN));
#endif
#if HAS_MICROSTEPS_Z
SERIAL_PROTOCOLPGM("Z: ");
SERIAL_PROTOCOL(READ(Z_MS1_PIN));
SERIAL_PROTOCOLLN(READ(Z_MS2_PIN));
#endif
#if HAS_MICROSTEPS_E0
SERIAL_PROTOCOLPGM("E0: ");
SERIAL_PROTOCOL(READ(E0_MS1_PIN));
SERIAL_PROTOCOLLN(READ(E0_MS2_PIN));
#endif
#if HAS_MICROSTEPS_E1
SERIAL_PROTOCOLPGM("E1: ");
SERIAL_PROTOCOL(READ(E1_MS1_PIN));
SERIAL_PROTOCOLLN(READ(E1_MS2_PIN));
#endif
}
#endif // HAS_MICROSTEPS