Skip to content

Commit

Permalink
⚡️ Smart Adaptive Multi-Stepping (MarlinFirmware#25474)
Browse files Browse the repository at this point in the history
  • Loading branch information
tombrazier authored and thinkyhead committed May 16, 2023
1 parent bfbf780 commit b9113be
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 71 deletions.
166 changes: 111 additions & 55 deletions Marlin/src/module/stepper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ uint32_t Stepper::acceleration_time, Stepper::deceleration_time;
uint8_t Stepper::steps_per_isr = 1; // Count of steps to perform per Stepper ISR call
#endif

#if DISABLED(OLD_ADAPTIVE_MULTISTEPPING)
hal_timer_t Stepper::time_spent_in_isr = 0, Stepper::time_spent_out_isr = 0;
#endif

#if ENABLED(FREEZE_FEATURE)
bool Stepper::frozen; // = false
#endif
Expand Down Expand Up @@ -614,27 +618,26 @@ void Stepper::set_directions() {
TERN_(HAS_V_DIR, SET_STEP_DIR(V));
TERN_(HAS_W_DIR, SET_STEP_DIR(W));

#if ENABLED(MIXING_EXTRUDER)
#if HAS_EXTRUDERS
// Because this is valid for the whole block we don't know
// what E steppers will step. Likely all. Set all.
if (motor_direction(E_AXIS)) {
MIXER_STEPPER_LOOP(j) REV_E_DIR(j);
count_direction.e = -1;
}
else {
MIXER_STEPPER_LOOP(j) NORM_E_DIR(j);
count_direction.e = 1;
}
#elif HAS_EXTRUDERS
if (motor_direction(E_AXIS)) {
REV_E_DIR(stepper_extruder);
#if ENABLED(MIXING_EXTRUDER)
MIXER_STEPPER_LOOP(j) REV_E_DIR(j);
#else
REV_E_DIR(stepper_extruder);
#endif
count_direction.e = -1;
}
else {
NORM_E_DIR(stepper_extruder);
#if ENABLED(MIXING_EXTRUDER)
MIXER_STEPPER_LOOP(j) NORM_E_DIR(j);
#else
NORM_E_DIR(stepper_extruder);
#endif
count_direction.e = 1;
}
#endif
#endif // HAS_EXTRUDERS

DIR_WAIT_AFTER();
}
Expand Down Expand Up @@ -1587,16 +1590,44 @@ void Stepper::isr() {
*/
min_ticks = HAL_timer_get_count(MF_TIMER_STEP) + hal_timer_t(TERN(__AVR__, 8, 1) * (STEPPER_TIMER_TICKS_PER_US));

/**
* NB: If for some reason the stepper monopolizes the MPU, eventually the
* timer will wrap around (and so will 'next_isr_ticks'). So, limit the
* loop to 10 iterations. Beyond that, there's no way to ensure correct pulse
* timing, since the MCU isn't fast enough.
*/
if (!--max_loops) next_isr_ticks = min_ticks;
#if ENABLED(OLD_ADAPTIVE_MULTISTEPPING)
/**
* NB: If for some reason the stepper monopolizes the MPU, eventually the
* timer will wrap around (and so will 'next_isr_ticks'). So, limit the
* loop to 10 iterations. Beyond that, there's no way to ensure correct pulse
* timing, since the MCU isn't fast enough.
*/
if (!--max_loops) next_isr_ticks = min_ticks;
#endif

// Advance pulses if not enough time to wait for the next ISR
} while (next_isr_ticks < min_ticks);
} while (TERN(OLD_ADAPTIVE_MULTISTEPPING, true, --max_loops) && next_isr_ticks < min_ticks);

#if DISABLED(OLD_ADAPTIVE_MULTISTEPPING)

// Track the time spent in the ISR
const hal_timer_t time_spent = HAL_timer_get_count(MF_TIMER_STEP);
time_spent_in_isr += time_spent;

if (next_isr_ticks < min_ticks) {
next_isr_ticks = min_ticks;

// When forced out of the ISR, increase multi-stepping
#if MULTISTEPPING_LIMIT > 1
if (steps_per_isr < MULTISTEPPING_LIMIT) {
steps_per_isr <<= 1;
// ticks_nominal will need to be recalculated if we are in cruise phase
ticks_nominal = 0;
}
#endif
}
else {
// Track the time spent voluntarily outside the ISR
time_spent_out_isr += next_isr_ticks;
time_spent_out_isr -= time_spent;
}

#endif // !OLD_ADAPTIVE_MULTISTEPPING

// Now 'next_isr_ticks' contains the period to the next Stepper ISR - And we are
// sure that the time has not arrived yet - Warrantied by the scheduler
Expand Down Expand Up @@ -2091,44 +2122,56 @@ hal_timer_t Stepper::calc_timer_interval(uint32_t step_rate) {

// Get the timer interval and the number of loops to perform per tick
hal_timer_t Stepper::calc_multistep_timer_interval(uint32_t step_rate) {
#if MULTISTEPPING_LIMIT == 1

// Just make sure the step rate is doable
NOMORE(step_rate, uint32_t(MAX_STEP_ISR_FREQUENCY_1X));
#if ENABLED(OLD_ADAPTIVE_MULTISTEPPING)

#else
#if MULTISTEPPING_LIMIT == 1

// The stepping frequency limits for each multistepping rate
static const uint32_t limit[] PROGMEM = {
( MAX_STEP_ISR_FREQUENCY_1X )
, ( MAX_STEP_ISR_FREQUENCY_2X >> 1)
#if MULTISTEPPING_LIMIT >= 4
, ( MAX_STEP_ISR_FREQUENCY_4X >> 2)
#endif
#if MULTISTEPPING_LIMIT >= 8
, ( MAX_STEP_ISR_FREQUENCY_8X >> 3)
#endif
#if MULTISTEPPING_LIMIT >= 16
, ( MAX_STEP_ISR_FREQUENCY_16X >> 4)
#endif
#if MULTISTEPPING_LIMIT >= 32
, ( MAX_STEP_ISR_FREQUENCY_32X >> 5)
#endif
#if MULTISTEPPING_LIMIT >= 64
, ( MAX_STEP_ISR_FREQUENCY_64X >> 6)
#endif
#if MULTISTEPPING_LIMIT >= 128
, (MAX_STEP_ISR_FREQUENCY_128X >> 7)
#endif
};
// Just make sure the step rate is doable
NOMORE(step_rate, uint32_t(MAX_STEP_ISR_FREQUENCY_1X));

// Find a doable step rate using multistepping
uint8_t multistep = 1;
for (uint8_t i = 0; i < COUNT(limit) && step_rate > uint32_t(pgm_read_dword(&limit[i])); ++i) {
step_rate >>= 1;
multistep <<= 1;
}
steps_per_isr = multistep;
#else

// The stepping frequency limits for each multistepping rate
static const uint32_t limit[] PROGMEM = {
( MAX_STEP_ISR_FREQUENCY_1X )
, (((F_CPU) / ISR_EXECUTION_CYCLES(1)) >> 1)
#if MULTISTEPPING_LIMIT >= 4
, (((F_CPU) / ISR_EXECUTION_CYCLES(2)) >> 2)
#endif
#if MULTISTEPPING_LIMIT >= 8
, (((F_CPU) / ISR_EXECUTION_CYCLES(3)) >> 3)
#endif
#if MULTISTEPPING_LIMIT >= 16
, (((F_CPU) / ISR_EXECUTION_CYCLES(4)) >> 4)
#endif
#if MULTISTEPPING_LIMIT >= 32
, (((F_CPU) / ISR_EXECUTION_CYCLES(5)) >> 5)
#endif
#if MULTISTEPPING_LIMIT >= 64
, (((F_CPU) / ISR_EXECUTION_CYCLES(6)) >> 6)
#endif
#if MULTISTEPPING_LIMIT >= 128
, (((F_CPU) / ISR_EXECUTION_CYCLES(7)) >> 7)
#endif
};

// Find a doable step rate using multistepping
uint8_t multistep = 1;
for (uint8_t i = 0; i < COUNT(limit) && step_rate > uint32_t(pgm_read_dword(&limit[i])); ++i) {
step_rate >>= 1;
multistep <<= 1;
}
steps_per_isr = multistep;

#endif

#elif MULTISTEPPING_LIMIT > 1

uint8_t loops = steps_per_isr;
if (MULTISTEPPING_LIMIT >= 16 && loops >= 16) { step_rate >>= 4; loops >>= 4; }
if (MULTISTEPPING_LIMIT >= 4 && loops >= 4) { step_rate >>= 2; loops >>= 2; }
if (MULTISTEPPING_LIMIT >= 2 && loops >= 2) { step_rate >>= 1; }

#endif

Expand All @@ -2141,6 +2184,19 @@ hal_timer_t Stepper::calc_multistep_timer_interval(uint32_t step_rate) {
* have been done, so it is less time critical.
*/
hal_timer_t Stepper::block_phase_isr() {
#if DISABLED(OLD_ADAPTIVE_MULTISTEPPING)
// If the ISR uses < 50% of MPU time, halve multi-stepping
const hal_timer_t time_spent = HAL_timer_get_count(MF_TIMER_STEP);
#if MULTISTEPPING_LIMIT > 1
if (steps_per_isr > 1 && time_spent_out_isr >= time_spent_in_isr + time_spent) {
steps_per_isr >>= 1;
// ticks_nominal will need to be recalculated if we are in cruise phase
ticks_nominal = 0;
}
#endif
time_spent_in_isr = -time_spent; // unsigned but guaranteed to be +ve when needed
time_spent_out_isr = 0;
#endif

// If no queued movements, just wait 1ms for the next block
hal_timer_t interval = (STEPPER_TIMER_RATE) / 1000UL;
Expand Down
29 changes: 13 additions & 16 deletions Marlin/src/module/stepper.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@
#error "Expected at least one of MINIMUM_STEPPER_PULSE or MAXIMUM_STEPPER_RATE to be defined"
#endif

// The loop takes the base time plus the time for all the bresenham logic for R pulses plus the time
// between pulses for (R-1) pulses. But the user could be enforcing a minimum time so the loop time is:
// The loop takes the base time plus the time for all the bresenham logic for 1 << R pulses plus the time
// between pulses for ((1 << R) - 1) pulses. But the user could be enforcing a minimum time so the loop time is:
#define ISR_LOOP_CYCLES(R) ((ISR_LOOP_BASE_CYCLES + MIN_ISR_LOOP_CYCLES + MIN_STEPPER_PULSE_CYCLES) * ((1UL << R) - 1) + _MAX(MIN_ISR_LOOP_CYCLES, MIN_STEPPER_PULSE_CYCLES))

// Model input shaping as an extra loop call
#define ISR_SHAPING_LOOP_CYCLES(R) (TERN0(HAS_SHAPING, ((ISR_LOOP_BASE_CYCLES) + TERN0(INPUT_SHAPING_X, ISR_X_STEPPER_CYCLES) + TERN0(INPUT_SHAPING_Y, ISR_Y_STEPPER_CYCLES)) << R))
#define ISR_SHAPING_LOOP_CYCLES(R) (TERN0(HAS_SHAPING, (ISR_LOOP_BASE_CYCLES + TERN0(INPUT_SHAPING_X, ISR_X_STEPPER_CYCLES) + TERN0(INPUT_SHAPING_Y, ISR_Y_STEPPER_CYCLES)) << R))

// If linear advance is enabled, then it is handled separately
#if ENABLED(LIN_ADVANCE)
Expand All @@ -241,24 +241,17 @@
#define ISR_LA_LOOP_CYCLES 0UL
#endif

// Now estimate the total ISR execution time in cycles given a step per ISR multiplier
#define ISR_EXECUTION_CYCLES(R) (((ISR_BASE_CYCLES + ISR_S_CURVE_CYCLES + ISR_SHAPING_BASE_CYCLES + ISR_LOOP_CYCLES(R) + ISR_SHAPING_LOOP_CYCLES(R) + ISR_LA_BASE_CYCLES + ISR_LA_LOOP_CYCLES)) >> R)
// Estimate the total ISR execution time in cycles given a step-per-ISR shift multiplier
#define ISR_EXECUTION_CYCLES(R) ((ISR_BASE_CYCLES + ISR_S_CURVE_CYCLES + ISR_SHAPING_BASE_CYCLES + ISR_LOOP_CYCLES(R) + ISR_SHAPING_LOOP_CYCLES(R) + ISR_LA_BASE_CYCLES + ISR_LA_LOOP_CYCLES) >> R)

// The maximum allowable stepping frequency when doing x128-x1 stepping (in Hz)
#define MAX_STEP_ISR_FREQUENCY_128X ((F_CPU) / ISR_EXECUTION_CYCLES(7))
#define MAX_STEP_ISR_FREQUENCY_64X ((F_CPU) / ISR_EXECUTION_CYCLES(6))
#define MAX_STEP_ISR_FREQUENCY_32X ((F_CPU) / ISR_EXECUTION_CYCLES(5))
#define MAX_STEP_ISR_FREQUENCY_16X ((F_CPU) / ISR_EXECUTION_CYCLES(4))
#define MAX_STEP_ISR_FREQUENCY_8X ((F_CPU) / ISR_EXECUTION_CYCLES(3))
#define MAX_STEP_ISR_FREQUENCY_4X ((F_CPU) / ISR_EXECUTION_CYCLES(2))
#define MAX_STEP_ISR_FREQUENCY_2X ((F_CPU) / ISR_EXECUTION_CYCLES(1))
#define MAX_STEP_ISR_FREQUENCY_1X ((F_CPU) / ISR_EXECUTION_CYCLES(0))
// The maximum allowable stepping frequency when doing 1x stepping (in Hz)
#define MAX_STEP_ISR_FREQUENCY_1X ((F_CPU) / ISR_EXECUTION_CYCLES(0))

// The minimum step ISR rate used by ADAPTIVE_STEP_SMOOTHING to target 50% CPU usage
// This does not account for the possibility of multi-stepping.
// Should a MULTISTEPPING_LIMIT of 1 should be required with ADAPTIVE_STEP_SMOOTHING?
#define MIN_STEP_ISR_FREQUENCY (MAX_STEP_ISR_FREQUENCY_1X / 2)
#define MIN_STEP_ISR_FREQUENCY (MAX_STEP_ISR_FREQUENCY_1X >> 1)

// Number of axes that could be enabled/disabled. Dual/multiple steppers are combined.
#define ENABLE_COUNT (NUM_AXES + E_STEPPERS)
typedef bits_t(ENABLE_COUNT) ena_mask_t;

Expand Down Expand Up @@ -547,6 +540,10 @@ class Stepper {
static uint8_t steps_per_isr;
#endif

#if DISABLED(OLD_ADAPTIVE_MULTISTEPPING)
static hal_timer_t time_spent_in_isr, time_spent_out_isr;
#endif

#if ENABLED(ADAPTIVE_STEP_SMOOTHING)
static uint8_t oversampling_factor; // Oversampling factor (log2(multiplier)) to increase temporal resolution of axis
#else
Expand Down

0 comments on commit b9113be

Please sign in to comment.