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
|
// Call the ISR scheduler
|
||||||
hal_timer_t ticks = Stepper::isr_scheduler();
|
hal_timer_t ticks = Stepper::isr_scheduler();
|
||||||
|
|
||||||
// Now 'ticks' contains the period to the next Stepper ISR.
|
// Now 'ticks' contains the period to the next Stepper ISR - And we are
|
||||||
// Potential problem: Since the timer continues to run, the requested
|
// sure that the time has not arrived yet - Warrantied by the scheduler
|
||||||
// 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))));
|
|
||||||
|
|
||||||
// Set the next ISR to fire at the proper time
|
// Set the next ISR to fire at the proper time
|
||||||
HAL_timer_set_compare(STEP_TIMER_NUM, ticks);
|
HAL_timer_set_compare(STEP_TIMER_NUM, ticks);
|
||||||
|
@ -1178,6 +1170,15 @@ HAL_STEP_TIMER_ISR {
|
||||||
hal_timer_t Stepper::isr_scheduler() {
|
hal_timer_t Stepper::isr_scheduler() {
|
||||||
uint32_t interval;
|
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
|
// Run main stepping pulse phase ISR if we have to
|
||||||
if (!nextMainISR) Stepper::stepper_pulse_phase_isr();
|
if (!nextMainISR) Stepper::stepper_pulse_phase_isr();
|
||||||
|
|
||||||
|
@ -1193,39 +1194,81 @@ hal_timer_t Stepper::isr_scheduler() {
|
||||||
|
|
||||||
#if ENABLED(LIN_ADVANCE)
|
#if ENABLED(LIN_ADVANCE)
|
||||||
// Select the closest interval in time
|
// Select the closest interval in time
|
||||||
interval = (nextAdvanceISR <= nextMainISR)
|
interval = (nextAdvanceISR <= nextMainISR) ? nextAdvanceISR : nextMainISR;
|
||||||
? nextAdvanceISR
|
#else
|
||||||
: nextMainISR;
|
|
||||||
|
|
||||||
#else // !ENABLED(LIN_ADVANCE)
|
|
||||||
|
|
||||||
// The interval is just the remaining time to the stepper ISR
|
// The interval is just the remaining time to the stepper ISR
|
||||||
interval = nextMainISR;
|
interval = nextMainISR;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Limit the value to the maximum possible value of the timer
|
// Limit the value to the maximum possible value of the timer
|
||||||
if (interval > HAL_TIMER_TYPE_MAX)
|
NOMORE(interval, HAL_TIMER_TYPE_MAX);
|
||||||
interval = HAL_TIMER_TYPE_MAX;
|
|
||||||
|
|
||||||
// Compute the time remaining for the main isr
|
// Compute the time remaining for the main isr
|
||||||
nextMainISR -= interval;
|
nextMainISR -= interval;
|
||||||
|
|
||||||
#if ENABLED(LIN_ADVANCE)
|
#if ENABLED(LIN_ADVANCE)
|
||||||
// Compute the time remaining for the advance isr
|
// Compute the time remaining for the advance isr
|
||||||
if (nextAdvanceISR != ADV_NEVER)
|
if (nextAdvanceISR != ADV_NEVER) nextAdvanceISR -= interval;
|
||||||
nextAdvanceISR -= interval;
|
|
||||||
#endif
|
#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
|
* This phase of the ISR should ONLY create the pulses for the steppers.
|
||||||
// the pulses should be generated (when the interrupt triggers) to the
|
* This prevents jitter caused by the interval between the start of the
|
||||||
// time pulses are actually created. So, PLEASE DO NOT PLACE ANY CODE
|
* interrupt and the start of the pulses. DON'T add any logic ahead of the
|
||||||
// above this line that can conditionally change that time (we are trying
|
* call to this method that might cause variation in the timing. The aim
|
||||||
// to keep the delay between the interrupt triggering and pulse generation
|
* is to keep pulse timing as regular as possible.
|
||||||
// as constant as possible!!!!
|
*/
|
||||||
void Stepper::stepper_pulse_phase_isr() {
|
void Stepper::stepper_pulse_phase_isr() {
|
||||||
|
|
||||||
// If we must abort the current block, do so!
|
// If we must abort the current block, do so!
|
||||||
|
|
Reference in a new issue