Move the branch accel_clock into the attic.

This was a very interesting approach, but for the forseeable
future it's unlikely the code will replace the current one.

However, many parts of it were already moved to the experimental
branch. It turns out the approach with recalculating acceleration
at a constant time interval is exactly right, but works much more
precisely when keeping maths step-based.
This commit is contained in:
Markus Hitter 2013-10-27 20:31:36 +01:00
parent 5df8eeaacf
commit 1ab42ee8eb
3 changed files with 1635 additions and 0 deletions

View File

@ -0,0 +1,363 @@
From 1d0b7d8d67c33a6dc6d96ae5de7cc40c755a79c1 Mon Sep 17 00:00:00 2001
From: Markus Hitter <mah@jump-ing.de>
Date: Mon, 25 Mar 2013 11:19:50 +0100
Subject: Implement ACCELERATION_CLOCK.
This was tried a lot, with the following results:
1. Doing acceleration calculations (and later, curvature
calculations) in an clock based interrupt instead of
the step interrupt is an excellent idea. Achievable
step rate raised from about 16.000 steps/second
to 48.000 steps/second.
2. The approach to calculate desired speeds from movement time
did work not so well. While it's possible to keep geometrical
accuracy (continue at minimum speed or stop before decelerating
to full stop in case the timing doesn't match), timing
calculations are way to inprecise to match a movement's end
within +- one step. Missing the movement end by more than 10
steps was observed regularly.
3. Major reasons for 2. are apparently inprecise distance
and timer calculations. Even accumulating just 1% of
inprecision means more than 100 missed steps at the end
of a long move.
4. To avoid 2., the next approach shall turn back to calculate
speeds based on executed steps, like it was done in the step
interrupt.
ACCELERATION_CLOCK is an approach different from the other ones.
Acceleration isn't calculated as part of the step interrupt, but
on clock based intervals (every 1ms or 2 ms). This not only
allows to do these calculations with 16 bit integers, it also
reduces the number of these expensive calculations at high speeds.
The step interrupt becomes very lean, doing only Bresenham
calculations, and should allow much higher step rates (several
steps can be done per acceleration calculation). 500 to 1000
speed calculations per second should be more than sufficient
to give a smooth ride.
It should be possible to combine this with ACCELERATION_TEMPORAL
to give equally spaced steps for _every_ stepper for an even
smoother ride.
More ACCELERATION_CLOCK.
ACCELERATION_CLOCK: add more refinements and debug code.
For yet unknown reasons, this strategy falls far below
expectations. Configured to 1280 steps/mm, the code works
for up to about 500 mm/min, only. ACCELERATION_RAMPING does,
despite the expensive acceleration calculation in the step
interrupt, manage to move 760 mm/min.
For finding the cause, I tried to comment out virtually all
code out of dda_step() as well as the clock interrupt. Just
Bresenham for the X-axis and setTimer() left, the code still
acts funny at pretty much the same feedrates. No enhancement
at all.
The debug code currently put in sends a 's' on every step
interrupt, a '.' on every clock interrupt. At higher speeds,
one should see one '.' every 20 's' or similar. However,
from time to time one sees consecutive clock interrupts,
apparently step interrupts fail to happen in some
situations.
The good thing: acceleration works reasonably fine now, the
situation with clock ticks, and along with them, speed changes,
happening more often than actual steps seems to be solved. In
earlier code, the first speed calculation right after movement
start caused a long pause, leading to something like a delayed,
unaccelerated movement.
Get ACCELERATION_CLOCK finally working. Yikes!
The problem was: the step interrupt unlocked interrupts and
if this resulted in a rush of pending other interrupts,
it took a looong time until the step timer was set again.
---
dda.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
dda.h | 13 ++++++
2 files changed, 179 insertions(+), 3 deletions(-)
diff --git a/dda.c b/dda.c
index a02f37d..e84f91e 100644
--- a/dda.c
+++ b/dda.c
@@ -25,6 +25,11 @@
#ifdef DC_EXTRUDER
#include "heater.h"
#endif
+#include "delay.h"
+
+#if defined ACCELERATION_RAMPING && defined ACCELERATION_CLOCK
+ #error Cant define ACCELERATION_RAMPING and ACCELERATION_CLOCK at the same time.
+#endif
/*
position tracking
@@ -347,6 +352,82 @@ void dda_create(DDA *dda, TARGET *target, DDA *prev_dda) {
#ifdef LOOKAHEAD
dda_join_moves(prev_dda, dda);
#endif
+ #elif defined ACCELERATION_CLOCK
+ uint16_t candidate;
+
+ // Total time of the unaccelerated move.
+ // 1 um/ms = 1 mm/s = 60 mm/min
+ dda->time_total = distance * (60 / TICK_TIME_MS) / target->F;
+
+ // To avoid overspeeding an axis, movement takes at least as
+ // long as the slowest axis requires.
+ candidate = x_delta_um * (60 / TICK_TIME_MS) / MAXIMUM_FEEDRATE_X;
+ if (candidate > dda->time_total)
+ dda->time_total = candidate;
+ candidate = y_delta_um * (60 / TICK_TIME_MS) / MAXIMUM_FEEDRATE_Y;
+ if (candidate > dda->time_total)
+ dda->time_total = candidate;
+ candidate = z_delta_um * (60 / TICK_TIME_MS) / MAXIMUM_FEEDRATE_Z;
+ if (candidate > dda->time_total)
+ dda->time_total = candidate;
+ candidate = e_delta_um * (60 / TICK_TIME_MS) / MAXIMUM_FEEDRATE_E;
+ if (candidate > dda->time_total)
+ dda->time_total = candidate;
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ sersendf_P(PSTR("time total %u\n"), dda->time_total);
+
+ // Re-calculate speeds, as they might have changed.
+ dda->F_start = 0;
+ dda->F_end = 0;
+ dda->F_max = distance * (60 / TICK_TIME_MS) / dda->time_total;
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ sersendf_P(PSTR("corrected F_max %u\n"), dda->F_max);
+
+ // Time in clock ticks required for acceleration.
+ dda->time_accel = ((uint32_t)(dda->F_max - dda->F_start)) *
+ ((uint32_t)(1000 / TICK_TIME_MS)) /
+ ((uint32_t)(60 * ACCELERATION));
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ sersendf_P(PSTR("time accel %u\n"), dda->time_accel);
+
+ // Time in clock ticks required for deceleration.
+ dda->time_decel = ((uint32_t)(dda->F_max - dda->F_end)) *
+ ((uint32_t)(1000 / TICK_TIME_MS)) /
+ ((uint32_t)(60 * ACCELERATION));
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ sersendf_P(PSTR("time decel %u\n"), dda->time_decel);
+
+ // Add time required for acceleration / deceleration.
+ dda->time_total += dda->time_accel / 2 + dda->time_decel / 2;
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ sersendf_P(PSTR("time total w. accel %u\n"), dda->time_total);
+ dda->time_decel = dda->time_total - dda->time_decel;
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ sersendf_P(PSTR("time decel2 %u\n"), dda->time_decel);
+
+ // UGLY HACK: to compensate for inaccurate ac- and deceleration
+ // time calculations, add a margin here:
+// dda->time_decel += (dda->time_accel >> 3);
+//sersendf_P(PSTR("time decel hacked %u\n"), dda->time_decel); delay_ms(10);
+
+ // This is the ratio between F (in mm/min) and c (in CPU clock ticks)
+ // and is constant during the entire move, even on curved movements.
+ // Essentially, it's the step rate of the fastest stepping stepper
+ // of the entire move.
+ // For linear movements it's simple:
+ dda->f_to_c = (distance * 2400L) / dda->total_steps * (F_CPU / 40000) << 8;
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ sersendf_P(PSTR("f_to_c %lu\n"), dda->f_to_c);
+
+ // Initial step delays. As we can't start with zero speed, advance
+ // all calculations by half a clock tick.
+ // v = a * t; c = 1 / v;
+// Don't forget F_start!
+ dda->c = dda->f_to_c / ((uint32_t)ACCELERATION * 60UL * TICK_TIME_MS / 1000UL);
+
+sersendf_P(PSTR("c_min new %lu\n"), dda->f_to_c / target->F); delay_ms(10);
+sersendf_P(PSTR("c_min trad %lu\n"), (move_duration / target->F) << 8); delay_ms(10);
+
#elif defined ACCELERATION_TEMPORAL
// TODO: limit speed of individual axes to MAXIMUM_FEEDRATE
// TODO: calculate acceleration/deceleration for each axis
@@ -445,6 +526,10 @@ void dda_start(DDA *dda) {
else
setTimer(move_state.c >> 8);
#else
+ #ifdef ACCELERATION_CLOCK
+ move_state.time_current = 0;
+ move_state.ticks_since_step = 0;
+ #endif
setTimer(dda->c >> 8);
#endif
}
@@ -537,7 +622,7 @@ void dda_step(DDA *dda) {
}
#endif
- #if STEP_INTERRUPT_INTERRUPTIBLE
+ #if defined STEP_INTERRUPT_INTERRUPTIBLE && ! defined ACCELERATION_CLOCK
// Since we have sent steps to all the motors that will be stepping
// and the rest of this function isn't so time critical, this interrupt
// can now be interruptible by other interrupts.
@@ -582,7 +667,6 @@ void dda_step(DDA *dda) {
//if (move_state.step_no == 0) {
// sersendf_P(PSTR("\r\nc %lu c_min %lu n %d"), dda->c, dda->c_min, move_state.n);
//}
-
recalc_speed = 0;
if (move_state.step_no < dda->rampup_steps) {
if (move_state.n < 0) // wrong ramp direction
@@ -678,6 +762,11 @@ void dda_step(DDA *dda) {
#endif
) {
dda->live = 0;
+ #ifdef ACCELERATION_CLOCK
+ if (dda->time_total - move_state.time_current > 1)
+ sersendf_P(PSTR("undershoot by %u ticks\n"),
+ dda->time_total - move_state.time_current);
+ #endif
#ifdef LOOKAHEAD
// If look-ahead was using this move, it could have missed our activation:
// make sure the ids do not match.
@@ -701,6 +790,9 @@ void dda_step(DDA *dda) {
else
setTimer(move_state.c >> 8);
#else
+ #ifdef ACCELERATION_CLOCK
+ move_state.ticks_since_step = 0;
+ #endif
setTimer(dda->c >> 8);
#endif
@@ -728,6 +820,8 @@ void dda_clock() {
static DDA *last_dda = NULL;
static uint8_t endstop_stop = 0; ///< Stop due to endstop trigger
+ move_state.time_current++;
+
dda = queue_current_movement();
if (dda != last_dda) {
move_state.debounce_count_xmin = move_state.debounce_count_ymin =
@@ -742,8 +836,11 @@ void dda_clock() {
// Lengthy calculations ahead!
// Make sure we didn't re-enter, then allow nested interrupts.
- if (busy)
+ if (busy) {
+ if (DEBUG_DDA && (debug_flags & DEBUG_DDA))
+ serial_writechar('B');
return;
+ }
busy = 1;
sei();
@@ -826,6 +923,72 @@ void dda_clock() {
}
} /* if (endstop_stop == 0) */
+ #ifdef ACCELERATION_CLOCK
+ uint32_t new_c = 0;
+ static uint8_t plateau_done = 0;
+
+ // Overtime?
+ if (move_state.time_current > dda->time_total) {
+ // Keep it short to have at least a chance to get it sent.
+ #warning Das hier hört nicht auf.
+ //serial_writestr_P(PSTR("ot"));
+ move_state.time_current = dda->time_total;
+ }
+ // Acceleration time.
+ else if (move_state.time_current < dda->time_accel) {
+ // v = a * t; c = 1 / v;
+ new_c = dda->f_to_c / ((uint32_t)ACCELERATION * 60UL * (uint32_t)move_state.time_current * TICK_TIME_MS / 1000UL);
+ plateau_done = 0;
+serial_writechar('a');
+ }
+ else if (move_state.time_current > dda->time_decel) {
+ uint32_t dt = (uint32_t)dda->time_total - (uint32_t)move_state.time_current;
+
+ if (dt < 1) // we undershot *sigh*
+ dt = 1;
+ // v = a * t; c = 1 / v;
+ new_c = dda->f_to_c / ((uint32_t)ACCELERATION * 60UL * dt * TICK_TIME_MS / 1000UL);
+serial_writechar('d');
+ plateau_done = 0;
+
+ }
+ // Plateau time.
+ else if (plateau_done == 0) {
+ new_c = dda->f_to_c / dda->F_max;
+ plateau_done = 1;
+serial_writechar('r');
+ }
+ else
+serial_writechar('.');
+
+ if (new_c) {
+ ATOMIC_START
+ dda->c = new_c;
+ ATOMIC_END
+ }
+
+ // Set up or readjust the timer if actual steps happen too slowly. dda_step()
+ // resets ticks_since_step to zero, while we increment it here, so we have an
+ // idea on how much time is gone since the last actual step.
+ // 300 = minimum time setTimer requires.
+if (dda->time_total == move_state.time_current)
+sersendf_P(PSTR("overshoot by %lu steps\n"), move_state.x_steps);
+ if (move_state.ticks_since_step) {
+ if ((dda->c >> 8) < ((uint32_t)move_state.ticks_since_step * TICK_TIME) + 300UL) {
+ // We're too late already, go as quick as possbile.
+ setTimer(300UL);
+serial_writechar('-');
+ }
+ else {
+ // TODO: we ignore the time taken until we get here.
+ setTimer((dda->c >> 8) - ((uint32_t)move_state.ticks_since_step * TICK_TIME));
+serial_writechar('+');
+ }
+ }
+ sei(); // setTimer locks interrupts
+ move_state.ticks_since_step++;
+ #endif
+
cli(); // Compensate sei() above.
busy = 0;
}
diff --git a/dda.h b/dda.h
index 5bc4238..76963ac 100644
--- a/dda.h
+++ b/dda.h
@@ -77,6 +77,10 @@ typedef struct {
/// tracking variable
int32_t n;
#endif
+ #ifdef ACCELERATION_CLOCK
+ uint16_t time_current;
+ uint8_t ticks_since_step;
+ #endif
#ifdef ACCELERATION_TEMPORAL
uint32_t x_time; ///< time of the last x step
uint32_t y_time; ///< time of the last y step
@@ -160,6 +164,15 @@ typedef struct {
uint8_t id;
#endif
#endif
+ #ifdef ACCELERATION_CLOCK
+ uint16_t F_start;
+ uint16_t F_end;
+ uint16_t F_max;
+ uint16_t time_accel; ///< in clock ticks (1ms or 2ms)
+ uint16_t time_decel; ///< in clock ticks (1ms or 2ms)
+ uint16_t time_total; ///< in clock ticks (1ms or 2ms)
+ uint32_t f_to_c;
+ #endif
#ifdef ACCELERATION_TEMPORAL
uint32_t x_step_interval; ///< time between steps on X axis
uint32_t y_step_interval; ///< time between steps on Y axis
--
1.8.3.2

1045
attic/accel_clock/dda.c Normal file

File diff suppressed because it is too large Load Diff

227
attic/accel_clock/dda.h Normal file
View File

@ -0,0 +1,227 @@
#ifndef _DDA_H
#define _DDA_H
#include <stdint.h>
#include "config.h"
#ifdef ACCELERATION_REPRAP
#ifdef ACCELERATION_RAMPING
#error Cant use ACCELERATION_REPRAP and ACCELERATION_RAMPING together.
#endif
#endif
/*
types
*/
// Enum to denote an axis
enum axis_e { X, Y, Z, E };
/**
\struct TARGET
\brief target is simply a point in space/time
X, Y, Z and E are in micrometers unless explcitely stated. F is in mm/min.
*/
typedef struct {
// TODO TODO: We should really make up a loop for all axes.
// Think of what happens when a sixth axis (multi colour extruder)
// appears?
int32_t X;
int32_t Y;
int32_t Z;
int32_t E;
uint32_t F;
uint8_t e_relative :1; ///< bool: e axis relative? Overrides all_relative
} TARGET;
/**
\struct VECTOR4D
\brief 4 dimensional vector used to describe the difference between moves.
Units are in micrometers and usually based off 'TARGET'.
*/
typedef struct {
int32_t X;
int32_t Y;
int32_t Z;
int32_t E;
} VECTOR4D;
/**
\struct MOVE_STATE
\brief this struct is made for tracking the current state of the movement
Parts of this struct are initialised only once per reboot, so make sure dda_step() leaves them with a value compatible to begin a new movement at the end of the movement. Other parts are filled in by dda_start().
*/
typedef struct {
// bresenham counters
int32_t x_counter; ///< counter for total_steps vs this axis
int32_t y_counter; ///< counter for total_steps vs this axis
int32_t z_counter; ///< counter for total_steps vs this axis
int32_t e_counter; ///< counter for total_steps vs this axis
// step counters
uint32_t x_steps; ///< number of steps on X axis
uint32_t y_steps; ///< number of steps on Y axis
uint32_t z_steps; ///< number of steps on Z axis
uint32_t e_steps; ///< number of steps on E axis
#ifdef ACCELERATION_RAMPING
/// counts actual steps done
uint32_t step_no;
/// time until next step
uint32_t c;
/// tracking variable
int32_t n;
#endif
#ifdef ACCELERATION_CLOCK
uint16_t time_current;
uint8_t ticks_since_step;
#endif
#ifdef ACCELERATION_TEMPORAL
uint32_t x_time; ///< time of the last x step
uint32_t y_time; ///< time of the last y step
uint32_t z_time; ///< time of the last z step
uint32_t e_time; ///< time of the last e step
uint32_t all_time; ///< time of the last step of any axis
#endif
/// Endstop debouncing
uint8_t debounce_count_xmin, debounce_count_ymin, debounce_count_zmin;
uint8_t debounce_count_xmax, debounce_count_ymax, debounce_count_zmax;
} MOVE_STATE;
/**
\struct DDA
\brief this is a digital differential analyser data struct
This struct holds all the details of an individual multi-axis move, including pre-calculated acceleration data.
This struct is filled in by dda_create(), called from enqueue(), called mostly from gcode_process() and from a few other places too (eg \file homing.c)
*/
typedef struct {
/// this is where we should finish
TARGET endpoint;
union {
struct {
// status fields
uint8_t nullmove :1; ///< bool: no axes move, maybe we wait for temperatures or change speed
uint8_t live :1; ///< bool: this DDA is running and still has steps to do
#ifdef ACCELERATION_REPRAP
uint8_t accel :1; ///< bool: speed changes during this move, run accel code
#endif
// wait for temperature to stabilise flag
uint8_t waitfor_temp :1; ///< bool: wait for temperatures to reach their set values
// directions
uint8_t x_direction :1; ///< direction flag for X axis
uint8_t y_direction :1; ///< direction flag for Y axis
uint8_t z_direction :1; ///< direction flag for Z axis
uint8_t e_direction :1; ///< direction flag for E axis
};
uint8_t allflags; ///< used for clearing all flags
};
// distances
uint32_t x_delta; ///< number of steps on X axis
uint32_t y_delta; ///< number of steps on Y axis
uint32_t z_delta; ///< number of steps on Z axis
uint32_t e_delta; ///< number of steps on E axis
/// total number of steps: set to \f$\max(\Delta x, \Delta y, \Delta z, \Delta e)\f$
uint32_t total_steps;
uint32_t c; ///< time until next step, 24.8 fixed point
#ifdef ACCELERATION_REPRAP
uint32_t end_c; ///< time between 2nd last step and last step
int32_t n; ///< precalculated step time offset variable. At every step we calculate \f$c = c - (2 c / n)\f$; \f$n+=4\f$. See http://www.embedded.com/columns/technicalinsights/56800129?printable=true for full description
#endif
#ifdef ACCELERATION_RAMPING
/// number of steps accelerating
uint32_t rampup_steps;
/// number of last step before decelerating
uint32_t rampdown_steps;
/// 24.8 fixed point timer value, maximum speed
uint32_t c_min;
#ifdef LOOKAHEAD
// With the look-ahead functionality, it is possible to retain physical
// movement between G1 moves. These variables keep track of the entry and
// exit speeds between moves.
uint32_t F_start;
uint32_t F_end;
// Displacement vector, in um, based between the difference of the starting
// point and the target. Required to obtain the jerk between 2 moves.
// Note: x_delta and co are in steps, not um.
VECTOR4D delta;
// Number the moves to be able to test at the end of lookahead if the moves
// are the same. Note: we do not need a lot of granularity here: more than
// MOVEBUFFER_SIZE is already enough.
uint8_t id;
#endif
#endif
#ifdef ACCELERATION_CLOCK
uint16_t F_start;
uint16_t F_end;
uint16_t F_max;
uint16_t time_accel; ///< in clock ticks (1ms or 2ms)
uint16_t time_decel; ///< in clock ticks (1ms or 2ms)
uint16_t time_total; ///< in clock ticks (1ms or 2ms)
uint32_t f_to_c;
#endif
#ifdef ACCELERATION_TEMPORAL
uint32_t x_step_interval; ///< time between steps on X axis
uint32_t y_step_interval; ///< time between steps on Y axis
uint32_t z_step_interval; ///< time between steps on Z axis
uint32_t e_step_interval; ///< time between steps on E axis
uint8_t axis_to_step; ///< axis to be stepped on the next interrupt
#endif
/// Endstop homing
uint8_t endstop_check; ///< Do we need to check endstops? 0x1=Check X, 0x2=Check Y, 0x4=Check Z
uint8_t endstop_stop_cond; ///< Endstop condition on which to stop motion: 0=Stop on detrigger, 1=Stop on trigger
} DDA;
/*
variables
*/
/// startpoint holds the endpoint of the most recently created DDA, so we know where the next one created starts. could also be called last_endpoint
extern TARGET startpoint;
/// the same as above, counted in motor steps
extern TARGET startpoint_steps;
/// current_position holds the machine's current position. this is only updated when we step, or when G92 (set home) is received.
extern TARGET current_position;
/*
methods
*/
// initialize dda structures
void dda_init(void);
// distribute a new startpoint
void dda_new_startpoint(void);
// create a DDA
void dda_create(DDA *dda, TARGET *target, DDA *prev_dda);
// start a created DDA (called from timer interrupt)
void dda_start(DDA *dda) __attribute__ ((hot));
// DDA takes one step (called from timer interrupt)
void dda_step(DDA *dda) __attribute__ ((hot));
// regular movement maintenance
void dda_clock(void);
// update current_position
void update_current_position(void);
#endif /* _DDA_H */