Firmware/FiveD on Arduino
FiveD on Arduino is a complete rewrite of the FiveD firmware by Triffid Hunter, which happens to fit into an ATmega168-based board, i.e. the Arduino Diecimila, just as well as into bigger sized controllers. It's main advantage is better performance.
This firmware is loosely based on the official FiveD Firmware, however it avoids C++ in favour of plain C, uses 100% integer math, and works very hard to minimize/eliminate long math operations in interrupt context.
Contents
Constant Acceleration
The official FiveD firmware changes speed by a fixed amount each step, however this also alters the step time. acceleration = dv/dt, so with a fixed dv but a changing dt, acceleration changes during a move. This makes high speeds quite unattainable as the acceleration quickly outstrips the motor's ability to keep up.
FiveD on Arduino contains a constant acceleration implementation.
Acceleration, RepRap Style
This is the default and matches the strategy of the official firmware. Each movement starts at the speed of the previous command and accelerates or decelerates to reach target speed at the end of the movement.
Goal of this strategy is to allow calculation of smooth movements in the host software. If communication between host and controller gets delayed, hard stops and starts are to be expected. This strategy isn't useful for commands issued by hand, either.
You can disable this behaviour in machine.h by commenting out
#define ACCELERATION_REPRAP
Acceleration, Start/Stop Ramping
This variation guarantees always smooth starts and stops, as all acceleration and deceleration is calculated in firmware (and actually uses less code than the above). Each movement starts at (almost) no speed, linearly accelerates to target speed and decelerates just in time to smoothly stop at the target. The steepness of acceleration and deceleration is configurable.
A drawback of the current implementation is, it stops after each move. This means slow average speeds in case of many short moves. It'll be possible to improve this situation by taking the next move into account when ramping down. A not-so-trivial task, predestined for a hacking week sometimes in the future.
Please note real-world machines in fact can't gain full speed instantly. Trying to do so results in bending and early wear out of the mechanical parts. Also, if acceleration is reliable, you can approximately double the speed of stepper motors without risking loss of steps. In tests with my setup I got over 1000 rpm with an ordinary NEMA-23 stepper!
As most current GCode generators expect RepRap-style acceleration and you can use only one style of acceleration, this feature is turned off by default. To use is, turn off ACCELERATION_REPRAP in machine.h and turn on:
#define ACCELERATION_RAMPING
This is enough for first tests (always recompile and re-upload the firmware, btw.), but to get best results, you should adjust ramping steppness as well:
#define ACCELERATION_STEEPNESS 500000
Smaller values give quicker acceleration. Tune it to what you think your machine can achieve. Sturdier machines with powerful motors can accelerate quick, weak machines with light motors need a bigger value.
Transmission of Multiple Commands
FiveD on Arduino has the capability to receive additional commands while working on the first one. This avoids short stops between commands and will allow for even more smooth transitions from one movement to the next in the future.
XON/XOFF Flow Control
FiveD on Arduino has XON/XOFF flow control implemented. This is not needed when sending GCode files via RepRap Host software, but quite helpful when sending files with a serial terminal emulator (GtkTerm, CoolTerm, HyperTerminal, etc.).
This feature is off by default, turn it on in machine.h by uncommenting
#define XONXOFF
Patches, Enhancements
If you have a change or a patch you want to discuss, or if you don't have github commit access, please upload the patch here.
To apply patches, type git apply
in your terminal, inside your local copy of git repo, then copy the text given her into the terminal and hit ctrl-d. Well, I hope it works that way.
If you work with git on FiveD on Arduino, please do commits in small pieces, one topic/bug at a time. This helps a lot in reviewing patches. That done, you can create a set of patches easily:
git format-patch --keep-subject -o out origin
You'll find each of your patches in the out/
directory, nicely equipped with a mail header and a proper file name.
Implement start/stop ramping.
This accounts for the fact real-world machines can't gain full speed instantly. In tests with my setup it also allowed more than doubling the target speed - over 1000 rpm with an ordinary NEMA-23 stepper!
The code allowing slow acceleration over the whole move had to be dropped. To be honest, I see no use in this code other than a hack in the wrong place (i.e. the G-code producing application) to achieve what's now done by firmware.
A fact of the current implementation is, it stops after each move. This means slow average speeds in case of many short moves.
It'll be possible to improve this situation by taking the next move into account when ramping down. A not-so-trivial task, predestined for a hacking week sometimes in the future.
--- mendel/dda.c | 122 ++++++++++++++++------------------------------------- mendel/dda.h | 38 ++++++++++++---- mendel/gcode.c | 2 - mendel/machine.h | 8 ++++ 4 files changed, 73 insertions(+), 97 deletions(-) diff --git a/mendel/dda.c b/mendel/dda.c index 9fdf6d3..bffbfa3 100644 --- a/mendel/dda.c +++ b/mendel/dda.c @@ -98,19 +98,6 @@ uint32_t delta32(uint32_t v1, uint32_t v2) { return v2 - v1; } -// this is an ultra-crude pseudo-logarithm routine, such that: -// 2 ^ msbloc(v) >= v -const uint8_t msbloc (uint32_t v) { - uint8_t i; - uint32_t c; - for (i = 31, c = 0x80000000; i; i--) { - if (v & c) - return i; - c >>= 1; - } - return 0; -} - /* CREATE a dda given current_position and a target, save to passed location so we can write directly into the queue */ @@ -210,66 +197,17 @@ void dda_create(DDA *dda, TARGET *target) { // distance * 2400 .. * F_CPU / 40000 so we can move a distance of up to 1800mm without overflowing uint32_t move_duration = ((distance * 2400) / dda->total_steps) * (F_CPU / 40000); + #ifdef ACCELERATION_RAMPING + dda->ramp_steps = dda->total_steps / 2; + dda->step_no = 0; // c is initial step time in IOclk ticks - dda->c = (move_duration / startpoint.F) << 8; - - if (debug_flags & DEBUG_DDA) { - serial_writestr_P(PSTR(",md:")); serwrite_uint32(move_duration); - serial_writestr_P(PSTR(",c:")); serwrite_uint32(dda->c >> 8); - } - - if (startpoint.F != target->F) { - uint32_t stF = startpoint.F / 4; - uint32_t enF = target->F / 4; - // now some constant acceleration stuff, courtesy of http://www.embedded.com/columns/technicalinsights/56800129?printable=true - uint32_t ssq = (stF * stF); - uint32_t esq = (enF * enF); - int32_t dsq = (int32_t) (esq - ssq) / 4; - - uint8_t msb_ssq = msbloc(ssq); - uint8_t msb_tot = msbloc(dda->total_steps); - - dda->end_c = (move_duration / target->F) << 8; - // the raw equation WILL overflow at high step rates, but 64 bit math routines take waay too much space - // at 65536 mm/min (1092mm/s), ssq/esq overflows, and dsq is also close to overflowing if esq/ssq is small - // but if ssq-esq is small, ssq/dsq is only a few bits - // we'll have to do it a few different ways depending on the msb locations of each - if ((msb_tot + msb_ssq) <= 30) { - // we have room to do all the multiplies first - if (debug_flags & DEBUG_DDA) - serial_writechar('A'); - dda->n = ((int32_t) (dda->total_steps * ssq) / dsq) + 1; - } - else if (msb_tot >= msb_ssq) { - // total steps has more precision - if (debug_flags & DEBUG_DDA) - serial_writechar('B'); - dda->n = (((int32_t) dda->total_steps / dsq) * (int32_t) ssq) + 1; - } - else { - // otherwise - if (debug_flags & DEBUG_DDA) - serial_writechar('C'); - dda->n = (((int32_t) ssq / dsq) * (int32_t) dda->total_steps) + 1; - } - - if (debug_flags & DEBUG_DDA) { -// serial_writestr_P(PSTR("\n{DDA:CA end_c:")); serwrite_uint32(dda->end_c >> 8); -// serial_writestr_P(PSTR(", n:")); serwrite_int32(dda->n); -// serial_writestr_P(PSTR(", md:")); serwrite_uint32(move_duration); -// serial_writestr_P(PSTR(", ssq:")); serwrite_uint32(ssq); -// serial_writestr_P(PSTR(", esq:")); serwrite_uint32(esq); -// serial_writestr_P(PSTR(", dsq:")); serwrite_int32(dsq); -// serial_writestr_P(PSTR(", msbssq:")); serwrite_uint8(msb_ssq); -// serial_writestr_P(PSTR(", msbtot:")); serwrite_uint8(msb_tot); -// serial_writestr_P(PSTR("}\n")); - sersendf_P(PSTR("\n{DDA:CA end_c:%lu, n:%ld, md:%lu, ssq:%lu, esq:%lu, dsq:%lu, msbssq:%u, msbtot:%u}\n"), dda->end_c >> 8, dda->n, move_duration, ssq, esq, dsq, msb_ssq, msb_tot); - } - - dda->accel = 1; - } - else - dda->accel = 0; + dda->c = ACCELERATION_STEEPNESS << 8; + dda->c_min = (move_duration / target->F) << 8; + dda->n = 1; + dda->ramp_state = RAMP_UP; + #else + dda->c = (move_duration / target->F) << 8; + #endif } if (debug_flags & DEBUG_DDA) @@ -424,22 +362,36 @@ void dda_step(DDA *dda) { sei(); #endif - // linear acceleration magic, courtesy of http://www.embedded.com/columns/technicalinsights/56800129?printable=true - if (dda->accel) { - if ( - ((dda->n > 0) && (dda->c > dda->end_c)) || - ((dda->n < 0) && (dda->c < dda->end_c)) - ) { - dda->c = (int32_t) dda->c - ((int32_t) (dda->c * 2) / dda->n); - dda->n += 4; - setTimer(dda->c >> 8); + #ifdef ACCELERATION_RAMPING + // - algorithm courtesy of http://www.embedded.com/columns/technicalinsights/56800129?printable=true + // - for simplicity, taking even/uneven number of steps into account dropped + // - number of steps moved is always accurate, speed might be one step off + switch (dda->ramp_state) { + case RAMP_UP: + case RAMP_MAX: + if (dda->step_no >= dda->ramp_steps) { + // RAMP_UP: time to decelerate before reaching maximum speed + // RAMP_MAX: time to decelerate + dda->ramp_state = RAMP_DOWN; + dda->n = -((int32_t)2) - dda->n; } - else if (dda->c != dda->end_c) { - dda->c = dda->end_c; - setTimer(dda->c >> 8); + if (dda->ramp_state == RAMP_MAX) + break; + case RAMP_DOWN: + dda->n += 4; + // be careful of signedness! + dda->c = (int32_t)dda->c - ((int32_t)(dda->c * 2) / dda->n); + if (dda->c <= dda->c_min) { + // maximum speed reached + dda->c = dda->c_min; + dda->ramp_state = RAMP_MAX; + dda->ramp_steps = dda->total_steps - dda->step_no; } - // else we are already at target speed + setTimer(dda->c >> 8); + break; } + dda->step_no++; + #endif if (step_option) { // we stepped, reset timeout diff --git a/mendel/dda.h b/mendel/dda.h index d775b70..9c250ed 100644 --- a/mendel/dda.h +++ b/mendel/dda.h @@ -7,6 +7,16 @@ #include "machine.h" /* + enums +*/ +// wether we accelerate, run at full speed, break down, etc. +typedef enum { + RAMP_UP, + RAMP_MAX, + RAMP_DOWN +} ramp_state_t; + +/* types */ @@ -25,18 +35,17 @@ typedef struct { TARGET endpoint; // status fields - uint8_t nullmove :1; - uint8_t live :1; - uint8_t accel :1; + uint8_t nullmove :1; + uint8_t live :1; // wait for temperature to stabilise flag uint8_t waitfor_temp :1; // directions - uint8_t x_direction :1; - uint8_t y_direction :1; - uint8_t z_direction :1; - uint8_t e_direction :1; + uint8_t x_direction :1; + uint8_t y_direction :1; + uint8_t z_direction :1; + uint8_t e_direction :1; // distances uint32_t x_delta; @@ -52,11 +61,20 @@ typedef struct { // total number of steps: set to max(x_delta, y_delta, z_delta, e_delta) uint32_t total_steps; - - // linear acceleration variables: c and end_c are 24.8 fixed point timer values, n is the tracking variable + // 24.8 fixed point timer value, initial speed uint32_t c; - uint32_t end_c; + + #ifdef ACCELERATION_RAMPING + // start of down-ramp, intitalized with total_steps / 2 + uint32_t ramp_steps; + // counts actual steps done + uint32_t step_no; + // 24.8 fixed point timer value, maximum speed + uint32_t c_min; + // tracking variable int32_t n; + ramp_state_t ramp_state; + #endif } DDA; /* diff --git a/mendel/gcode.c b/mendel/gcode.c index 5030ccb..5258ec2 100644 --- a/mendel/gcode.c +++ b/mendel/gcode.c @@ -694,8 +694,6 @@ void process_gcode_command(GCODE_COMMAND *gcmd) { serwrite_int32(movebuffer[mb_tail].endpoint.E); serial_writestr_P(PSTR(",F:")); serwrite_int32(movebuffer[mb_tail].endpoint.F); - serial_writestr_P(PSTR(",c:")); - serwrite_uint32(movebuffer[mb_tail].end_c); serial_writestr_P(PSTR("}\n")); print_queue(); diff --git a/mendel/machine.h b/mendel/machine.h index 067391e..b4d38d9 100644 --- a/mendel/machine.h +++ b/mendel/machine.h @@ -32,6 +32,14 @@ #define SEARCH_FEEDRATE_Z 50 #define SEARCH_FEEDRATE_E 50 +// acceleration and deceleration ramping +// undefine this for disabling the feature +#define ACCELERATION_RAMPING +// how fast to accelerate +// smaller values give quicker acceleration +// valid range = 1 to 8,000,000; 500,000 is a good starting point +#define ACCELERATION_STEEPNESS 500000 + // extruder settings #define TEMP_HYSTERESIS 20 #define TEMP_RESIDENCY_TIME 60 --