Fix up stepper ISR with linear advance timing (#10853)
Co-Authored-By: ejtagle <ejtagle@hotmail.com>
This commit is contained in:
parent
6f330f397e
commit
01d37e00af
1 changed files with 91 additions and 48 deletions
|
@ -1152,16 +1152,8 @@ HAL_STEP_TIMER_ISR {
|
|||
// Call the ISR scheduler
|
||||
hal_timer_t ticks = Stepper::isr_scheduler();
|
||||
|
||||
// Now 'ticks' contains the period to the next Stepper ISR.
|
||||
// Potential problem: Since the timer continues to run, the requested
|
||||
// compare value may already have passed.
|
||||
//
|
||||
// Assuming at least 6µs between calls to this ISR...
|
||||
// On AVR the ISR epilogue is estimated at 40 instructions - close to 2.5µS.
|
||||
// On ARM the ISR epilogue is estimated at 10 instructions - close to 200nS.
|
||||
// In either case leave at least 4µS for other tasks to execute.
|
||||
const hal_timer_t minticks = HAL_timer_get_count(STEP_TIMER_NUM) + hal_timer_t((HAL_TICKS_PER_US) * 4); // ISR never takes more than 1ms, so this shouldn't cause trouble
|
||||
NOLESS(ticks, MAX(minticks, hal_timer_t((STEP_TIMER_MIN_INTERVAL) * (HAL_TICKS_PER_US))));
|
||||
// Now 'ticks' contains the period to the next Stepper ISR - And we are
|
||||
// sure that the time has not arrived yet - Warrantied by the scheduler
|
||||
|
||||
// Set the next ISR to fire at the proper time
|
||||
HAL_timer_set_compare(STEP_TIMER_NUM, ticks);
|
||||
|
@ -1178,6 +1170,15 @@ HAL_STEP_TIMER_ISR {
|
|||
hal_timer_t Stepper::isr_scheduler() {
|
||||
uint32_t interval;
|
||||
|
||||
// Count of ticks for the next ISR
|
||||
hal_timer_t next_isr_ticks = 0;
|
||||
|
||||
// Limit the amount of iterations
|
||||
uint8_t max_loops = 10;
|
||||
|
||||
// We need this variable here to be able to use it in the following loop
|
||||
hal_timer_t min_ticks;
|
||||
do {
|
||||
// Run main stepping pulse phase ISR if we have to
|
||||
if (!nextMainISR) Stepper::stepper_pulse_phase_isr();
|
||||
|
||||
|
@ -1193,39 +1194,81 @@ hal_timer_t Stepper::isr_scheduler() {
|
|||
|
||||
#if ENABLED(LIN_ADVANCE)
|
||||
// Select the closest interval in time
|
||||
interval = (nextAdvanceISR <= nextMainISR)
|
||||
? nextAdvanceISR
|
||||
: nextMainISR;
|
||||
|
||||
#else // !ENABLED(LIN_ADVANCE)
|
||||
|
||||
interval = (nextAdvanceISR <= nextMainISR) ? nextAdvanceISR : nextMainISR;
|
||||
#else
|
||||
// The interval is just the remaining time to the stepper ISR
|
||||
interval = nextMainISR;
|
||||
#endif
|
||||
|
||||
// Limit the value to the maximum possible value of the timer
|
||||
if (interval > HAL_TIMER_TYPE_MAX)
|
||||
interval = HAL_TIMER_TYPE_MAX;
|
||||
NOMORE(interval, HAL_TIMER_TYPE_MAX);
|
||||
|
||||
// Compute the time remaining for the main isr
|
||||
nextMainISR -= interval;
|
||||
|
||||
#if ENABLED(LIN_ADVANCE)
|
||||
// Compute the time remaining for the advance isr
|
||||
if (nextAdvanceISR != ADV_NEVER)
|
||||
nextAdvanceISR -= interval;
|
||||
if (nextAdvanceISR != ADV_NEVER) nextAdvanceISR -= interval;
|
||||
#endif
|
||||
|
||||
return (hal_timer_t)interval;
|
||||
/**
|
||||
* This needs to avoid a race-condition caused by interleaving
|
||||
* of interrupts required by both the LA and Stepper algorithms.
|
||||
*
|
||||
* Assume the following tick times for stepper pulses:
|
||||
* Stepper ISR (S): 1 1000 2000 3000 4000
|
||||
* Linear Adv. (E): 10 1010 2010 3010 4010
|
||||
*
|
||||
* The current algorithm tries to interleave them, giving:
|
||||
* 1:S 10:E 1000:S 1010:E 2000:S 2010:E 3000:S 3010:E 4000:S 4010:E
|
||||
*
|
||||
* Ideal timing would yield these delta periods:
|
||||
* 1:S 9:E 990:S 10:E 990:S 10:E 990:S 10:E 990:S 10:E
|
||||
*
|
||||
* But, since each event must fire an ISR with a minimum duration, the
|
||||
* minimum delta might be 900, so deltas under 900 get rounded up:
|
||||
* 900:S d900:E d990:S d900:E d990:S d900:E d990:S d900:E d990:S d900:E
|
||||
*
|
||||
* It works, but divides the speed of all motors by half, leading to a sudden
|
||||
* reduction to 1/2 speed! Such jumps in speed lead to lost steps (not even
|
||||
* accounting for double/quad stepping, which makes it even worse).
|
||||
*/
|
||||
|
||||
// Compute the tick count for the next ISR
|
||||
next_isr_ticks += interval;
|
||||
|
||||
/**
|
||||
* Get the current tick value + margin
|
||||
* Assuming at least 6µs between calls to this ISR...
|
||||
* On AVR the ISR epilogue is estimated at 40 instructions - close to 2.5µS.
|
||||
* On ARM the ISR epilogue is estimated at 10 instructions - close to 200nS.
|
||||
* In either case leave at least 8µS for other tasks to execute - That allows
|
||||
* up to 100khz stepping rates
|
||||
*/
|
||||
min_ticks = HAL_timer_get_count(STEP_TIMER_NUM) + hal_timer_t((HAL_TICKS_PER_US) * 8); // ISR never takes more than 1ms, so this shouldn't cause trouble
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Advance pulses if not enough time to wait for the next ISR
|
||||
} while (next_isr_ticks < min_ticks);
|
||||
|
||||
// Return the count of ticks for the next ISR
|
||||
return (hal_timer_t)next_isr_ticks;
|
||||
}
|
||||
|
||||
// This part of the ISR should ONLY create the pulses for the steppers
|
||||
// -- Nothing more, nothing less -- We want to avoid jitter from where
|
||||
// the pulses should be generated (when the interrupt triggers) to the
|
||||
// time pulses are actually created. So, PLEASE DO NOT PLACE ANY CODE
|
||||
// above this line that can conditionally change that time (we are trying
|
||||
// to keep the delay between the interrupt triggering and pulse generation
|
||||
// as constant as possible!!!!
|
||||
/**
|
||||
* This phase of the ISR should ONLY create the pulses for the steppers.
|
||||
* This prevents jitter caused by the interval between the start of the
|
||||
* interrupt and the start of the pulses. DON'T add any logic ahead of the
|
||||
* call to this method that might cause variation in the timing. The aim
|
||||
* is to keep pulse timing as regular as possible.
|
||||
*/
|
||||
void Stepper::stepper_pulse_phase_isr() {
|
||||
|
||||
// If we must abort the current block, do so!
|
||||
|
|
Reference in a new issue