DDA: on short schedules, repeat step routine immediately ...

... instead of trying to fire an interrupt as quickly as possible.
This affects ACCELERATION_TEMPORAL only. It almost doubles the
achievable step rate. Measured maximum step rate (X axis only,
100 mm moves) is 40'000 steps/s on a 16 MHz electronics, so
approx. 50'000 steps/s on a 20 MHz controller, which is even
a bit faster than the ACCELERATION_RAMPING algorithm.

Tests with temporary test code were run and judging by these
tests, clock interrupts are now very reliable up to the point
where processing speed is simply exhaused.

Performance with ACCELERATION_RAMPING: this costs 10 bytes
binary size and exactly 2 clock cycles per step interrupt or
0.6% performance even. We could avoid this with a lot
of #ifdefs, but considering ACCELERATION_TEMPORAL will one
day be the default acceleration, skip these #ifdefs, also
for better code readability.

$ cd testcases
$ ./run-in-simulavr.sh short-moves.gcode smooth-curves.gcode triangle-odd.gcode
    SIZES             ATmega...  '168    '328(P)    '644(P)    '1280
    FLASH  : 20528 bytes         144%        67%        33%      16%
    RAM    :  2188 bytes         214%       107%        54%      27%
    EEPROM :    32 bytes           4%         2%         2%       1%

short-moves.gcode statistics:
LED on occurences: 838.
LED on time minimum: 304 clock cycles.
LED on time maximum: 715 clock cycles.
LED on time average: 310.717 clock cycles.

smooth-curves.gcode statistics:
LED on occurences: 8585.
LED on time minimum: 309 clock cycles.
LED on time maximum: 712 clock cycles.
LED on time average: 360.051 clock cycles.

triangle-odd.gcode statistics:
LED on occurences: 1636.
LED on time minimum: 304 clock cycles.
LED on time maximum: 710 clock cycles.
LED on time average: 332.32 clock cycles.
This commit is contained in:
Markus Hitter 2015-03-01 23:25:09 +01:00
parent 7dda91ab56
commit 9dd7426f9d
4 changed files with 98 additions and 66 deletions

92
dda.c
View File

@ -527,7 +527,7 @@ void dda_start(DDA *dda) {
dda->live = 1;
// set timeout for first step
timer_set(dda->c);
timer_set(dda->c, 0);
}
// else just a speed change, keep dda->live = 0
@ -644,47 +644,63 @@ void dda_step(DDA *dda) {
later. In turn we promise here to send a maximum of four such
short-delays consecutively and to give sufficient time on average.
*/
uint32_t c_candidate;
uint8_t i;
// This is the time which led to this call of dda_step().
move_state.last_time = move_state.time[dda->axis_to_step] +
dda->step_interval[dda->axis_to_step];
if (dda->axis_to_step == X) {
x_step();
move_state.steps[X]--;
move_state.time[X] += dda->step_interval[X];
move_state.last_time = move_state.time[X];
}
if (dda->axis_to_step == Y) {
y_step();
move_state.steps[Y]--;
move_state.time[Y] += dda->step_interval[Y];
move_state.last_time = move_state.time[Y];
}
if (dda->axis_to_step == Z) {
z_step();
move_state.steps[Z]--;
move_state.time[Z] += dda->step_interval[Z];
move_state.last_time = move_state.time[Z];
}
if (dda->axis_to_step == E) {
e_step();
move_state.steps[E]--;
move_state.time[E] += dda->step_interval[E];
move_state.last_time = move_state.time[E];
}
do {
uint32_t c_candidate;
enum axis_e i;
dda->c = 0xFFFFFFFF;
for (i = X; i < AXIS_COUNT; i++) {
if (move_state.steps[i]) {
c_candidate = move_state.time[i] + dda->step_interval[i] - move_state.last_time;
if (c_candidate < dda->c) {
dda->axis_to_step = i;
dda->c = c_candidate;
if (dda->axis_to_step == X) {
x_step();
move_state.steps[X]--;
move_state.time[X] += dda->step_interval[X];
}
if (dda->axis_to_step == Y) {
y_step();
move_state.steps[Y]--;
move_state.time[Y] += dda->step_interval[Y];
}
if (dda->axis_to_step == Z) {
z_step();
move_state.steps[Z]--;
move_state.time[Z] += dda->step_interval[Z];
}
if (dda->axis_to_step == E) {
e_step();
move_state.steps[E]--;
move_state.time[E] += dda->step_interval[E];
}
unstep();
// Find the next stepper to step.
dda->c = 0xFFFFFFFF;
for (i = X; i < AXIS_COUNT; i++) {
if (move_state.steps[i]) {
c_candidate = move_state.time[i] + dda->step_interval[i] -
move_state.last_time;
if (c_candidate < dda->c) {
dda->axis_to_step = i;
dda->c = c_candidate;
}
}
}
}
#endif
// No stepper to step found? Then we're done.
if (dda->c == 0xFFFFFFFF) {
dda->live = 0;
dda->done = 1;
break;
}
} while (timer_set(dda->c, 1));
#endif /* ACCELERATION_TEMPORAL */
// If there are no steps left or an endstop stop happened, we have finished.
//
// TODO: with ACCELERATION_TEMPORAL this duplicates some code. See where
// dda->live is zero'd, about 10 lines above.
if ((move_state.steps[X] == 0 && move_state.steps[Y] == 0 &&
move_state.steps[Z] == 0 && move_state.steps[E] == 0)
#ifdef ACCELERATION_RAMPING
@ -709,7 +725,9 @@ void dda_step(DDA *dda) {
}
else {
psu_timeout = 0;
timer_set(dda->c);
#ifndef ACCELERATION_TEMPORAL
timer_set(dda->c, 0);
#endif
}
// turn off step outputs, hopefully they've been on long enough by now to register with the drivers

View File

@ -82,7 +82,7 @@ void queue_step() {
DDA* current_movebuffer = &movebuffer[mb_tail];
if (current_movebuffer->live) {
if (current_movebuffer->waitfor_temp) {
timer_set(HEATER_WAIT_TIMEOUT);
timer_set(HEATER_WAIT_TIMEOUT, 0);
if (temp_achieved()) {
current_movebuffer->live = current_movebuffer->done = 0;
serial_writestr_P(PSTR("Temp achieved\n"));
@ -165,7 +165,7 @@ void next_move() {
if (current_movebuffer->waitfor_temp) {
serial_writestr_P(PSTR("Waiting for target temp\n"));
current_movebuffer->live = 1;
timer_set(HEATER_WAIT_TIMEOUT);
timer_set(HEATER_WAIT_TIMEOUT, 0);
}
else {
dda_start(current_movebuffer);

66
timer.c
View File

@ -101,16 +101,39 @@ void timer_init() {
/*! Specify how long until the step timer should fire.
\param delay in CPU ticks
\param check_short tells wether to check for impossibly short requests. This
should be set to 1 for calls from the step interrupt. Short requests
then return 1 and do not schedule a timer interrupt. The calling code
usually wants to handle this case.
Calls from elsewhere should set it to 0. In this case a timer
interrupt is always scheduled. At the risk that this scheduling
doesn't delay the requested time, but up to a full timer counter
overflow ( = 65536 / F_CPU = 3 to 4 milliseconds).
\return a flag wether the requested time was too short to allow scheduling
an interrupt. This is meaningful for ACCELERATION_TEMPORAL, where
requested delays can be zero or even negative. In this case, the
calling code should repeat the stepping code immediately and also
assume the timer to not change his idea of when the last step
happened.
Strategy of this timer is to schedule timer interrupts not starting at the
time of the call, but starting at the time of the previous timer interrupt
fired. This ignores the processing time taken in the step interrupt so far,
offering smooth and even step distribution. Flipside of this coin is,
schedules issued at an arbitrary time can result in drastically wrong delays.
See also discussion of parameter check_short and the return value.
This enables the step interrupt, but also disables interrupts globally.
So, if you use it from inside the step interrupt, make sure to do so
as late as possible. If you use it from outside the step interrupt,
do a sei() after it to make the interrupt actually fire.
*/
void timer_set(uint32_t delay) {
char timer_set(int32_t delay, char check_short) {
uint16_t step_start = 0;
#ifdef ACCELERATION_TEMPORAL
uint16_t current_time;
uint32_t earliest_time, actual_time;
#endif /* ACCELERATION_TEMPORAL */
// An interrupt would make all our timing calculations invalid,
@ -126,33 +149,22 @@ void timer_set(uint32_t delay) {
next_step_time = delay;
#ifdef ACCELERATION_TEMPORAL
// 300 = safe number of cpu cycles until the interrupt actually happens
current_time = TCNT1;
earliest_time = (uint32_t)current_time + 300;
if (current_time < step_start) // timer counter did overflow recently
earliest_time += 0x00010000;
actual_time = (uint32_t)step_start + next_step_time;
if (check_short) {
current_time = TCNT1;
// Setting the interrupt earlier than it can happen obviously doesn't
// make sense. To keep the "belongs to one move" idea, add an extra,
// remember this extra and compensate the extra if a longer delay comes in.
if (earliest_time > actual_time) {
step_extra_time += (earliest_time - actual_time);
next_step_time = earliest_time - (uint32_t)step_start;
}
else if (step_extra_time) {
if (step_extra_time < actual_time - earliest_time) {
next_step_time -= step_extra_time;
step_extra_time = 0;
}
else {
step_extra_time -= (actual_time - earliest_time);
next_step_time -= (actual_time - earliest_time);
}
}
// 200 = safe number of cpu cycles after current_time to allow a new
// interrupt happening. This is mostly the time needed to complete the
// current interrupt.
if ((current_time - step_start) + 200 > delay)
return 1;
}
#endif /* ACCELERATION_TEMPORAL */
// Now we know how long we actually want to delay, so set the timer.
// From here on we assume the requested delay is long enough to allow
// completion of the current interrupt before the next one is about to
// happen.
// Now we know how long we actually want to delay, so set the timer.
if (next_step_time < 65536) {
// set the comparator directly to the next real step
OCR1A = (next_step_time + step_start) & 0xFFFF;
@ -177,6 +189,8 @@ void timer_set(uint32_t delay) {
// Tell simulator
sim_timer_set();
#endif
return 0;
}
/// stop timers - emergency stop

View File

@ -26,7 +26,7 @@ timer stuff
*/
void timer_init(void) __attribute__ ((cold));
void timer_set(uint32_t delay);
char timer_set(int32_t delay, char check_short);
void timer_stop(void);