Updated comments. Changed stepper variable names to be more understandable. Added step locking note.

- Updated config comments and stepper code comments for the new changes.

- Changed stepper algorithm variable names to be more understandable in
what they actually do.

- Added a stepper lock note in default.h per user request.

- Started some code layout in handling feed holds and refactoring the
homing routine to use the main stepper algorithm instead of a seperate
version.
This commit is contained in:
Sonny Jeon 2013-10-29 08:31:48 -06:00
parent f7429ec79b
commit 27297d444b
7 changed files with 998 additions and 286 deletions

View File

@ -71,12 +71,13 @@
// NOTE: Make sure this value is less than 256, when adjusting both dependent parameters. // NOTE: Make sure this value is less than 256, when adjusting both dependent parameters.
#define ISR_TICKS_PER_ACCELERATION_TICK (ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND) #define ISR_TICKS_PER_ACCELERATION_TICK (ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND)
// The inverse time algorithm can use either floating point or long integers for its counters, but for // The inverse time algorithm can use either floating point or long integers for its counters (usually
// integers the counter values must be scaled since these values can be very small (10^-6). This // very small values ~10^-6), but with integers, the counter values must be scaled to be greater than
// multiplier value scales the floating point counter values for use in a long integer. Long integers // one. This multiplier value scales the floating point counter values for use in a long integer, which
// are finite so select the multiplier value high enough to avoid any numerical round-off issues and // are significantly faster to compute with a slightly higher precision ceiling than floats. Long
// still have enough range to account for all motion types. However, in most all imaginable CNC // integers are finite so select the multiplier value high enough to avoid any numerical round-off
// applications, the following multiplier value will work more than well enough. If you do have // issues and still have enough range to account for all motion types. However, in most all imaginable
// CNC applications, the following multiplier value will work more than well enough. If you do have
// happened to weird stepper motion issues, try modifying this value by adding or subtracting a // happened to weird stepper motion issues, try modifying this value by adding or subtracting a
// zero and report it to the Grbl administrators. // zero and report it to the Grbl administrators.
#define INV_TIME_MULTIPLIER 10000000.0 #define INV_TIME_MULTIPLIER 10000000.0

View File

@ -50,7 +50,7 @@
#define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm #define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm #define DEFAULT_Y_MAX_TRAVEL 200 // mm
@ -84,7 +84,7 @@
#define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm #define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm #define DEFAULT_Y_MAX_TRAVEL 200 // mm
@ -121,7 +121,7 @@
#define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 255 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 255 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm #define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm #define DEFAULT_Y_MAX_TRAVEL 200 // mm
@ -156,7 +156,7 @@
#define DEFAULT_HOMING_FEEDRATE 50.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 50.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm #define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm #define DEFAULT_Y_MAX_TRAVEL 200 // mm

View File

@ -59,12 +59,6 @@ void limits_init()
// your e-stop switch to the Arduino reset pin, since it is the most correct way to do this. // your e-stop switch to the Arduino reset pin, since it is the most correct way to do this.
ISR(LIMIT_INT_vect) ISR(LIMIT_INT_vect)
{ {
// TODO: This interrupt may be used to manage the homing cycle directly with the main stepper
// interrupt without adding too much to it. All it would need is some way to stop one axis
// when its limit is triggered and continue the others. This may reduce some of the code, but
// would make Grbl a little harder to read and understand down road. Holding off on this until
// we move on to new hardware or flash space becomes an issue. If it ain't broke, don't fix it.
// Ignore limit switches if already in an alarm state or in-process of executing an alarm. // Ignore limit switches if already in an alarm state or in-process of executing an alarm.
// When in the alarm state, Grbl should have been reset or will force a reset, so any pending // When in the alarm state, Grbl should have been reset or will force a reset, so any pending
// moves in the planner and serial buffers are all cleared and newly sent blocks will be // moves in the planner and serial buffers are all cleared and newly sent blocks will be
@ -89,6 +83,19 @@ ISR(LIMIT_INT_vect)
// NOTE: Only the abort runtime command can interrupt this process. // NOTE: Only the abort runtime command can interrupt this process.
static void homing_cycle(uint8_t cycle_mask, int8_t pos_dir, bool invert_pin, float homing_rate) static void homing_cycle(uint8_t cycle_mask, int8_t pos_dir, bool invert_pin, float homing_rate)
{ {
/* TODO: Change homing routine to call planner instead moving at the maximum seek rates
and (max_travel+10mm?) for each axes during the search phase. The routine should monitor
the state of the limit pins and when a pin is triggered, it can disable that axes by
setting the respective step_x, step_y, or step_z value in the executing planner block.
This keeps the stepper algorithm counters from triggering the step on that particular
axis. When all axes have been triggered, we can then disable the steppers and reset
the stepper and planner buffers. This same method can be used for the locate cycles.
This will also fix the slow max feedrate of the homing 'lite' stepper algorithm.
Need to check if setting the planner steps will require them to be volatile or not. */
// Determine governing axes with finest step resolution per distance for the Bresenham // Determine governing axes with finest step resolution per distance for the Bresenham
// algorithm. This solves the issue when homing multiple axes that have different // algorithm. This solves the issue when homing multiple axes that have different
// resolutions without exceeding system acceleration setting. It doesn't have to be // resolutions without exceeding system acceleration setting. It doesn't have to be

View File

@ -104,7 +104,9 @@ ISR(PINOUT_INT_vect)
// limit switches, or the main program. // limit switches, or the main program.
void protocol_execute_runtime() void protocol_execute_runtime()
{ {
// Reload step segment buffer
st_prep_buffer(); st_prep_buffer();
if (sys.execute) { // Enter only if any bit flag is true if (sys.execute) { // Enter only if any bit flag is true
uint8_t rt_exec = sys.execute; // Avoid calling volatile multiple times uint8_t rt_exec = sys.execute; // Avoid calling volatile multiple times

160
stepper.c
View File

@ -55,9 +55,9 @@ typedef struct {
uint8_t segment_steps_remaining; // Steps remaining in line segment motion uint8_t segment_steps_remaining; // Steps remaining in line segment motion
// Used by inverse time algorithm to track step rate // Used by inverse time algorithm to track step rate
int32_t counter_d; // Inverse time distance traveled since last step event int32_t counter_dist; // Inverse time distance traveled since last step event
uint32_t delta_d; // Inverse time distance traveled per interrupt tick uint32_t ramp_rate; // Inverse time distance traveled per interrupt tick
uint32_t d_per_tick; uint32_t dist_per_tick;
// Used by the stepper driver interrupt // Used by the stepper driver interrupt
uint8_t execute_step; // Flags step execution for each interrupt. uint8_t execute_step; // Flags step execution for each interrupt.
@ -65,7 +65,7 @@ typedef struct {
uint8_t out_bits; // The next stepping-bits to be output uint8_t out_bits; // The next stepping-bits to be output
uint8_t load_flag; uint8_t load_flag;
uint8_t ramp_count; uint8_t counter_ramp;
uint8_t ramp_type; uint8_t ramp_type;
} stepper_t; } stepper_t;
static stepper_t st; static stepper_t st;
@ -76,7 +76,7 @@ static stepper_t st;
// the number of accessible stepper buffer segments (SEGMENT_BUFFER_SIZE-1). // the number of accessible stepper buffer segments (SEGMENT_BUFFER_SIZE-1).
typedef struct { typedef struct {
int32_t step_events_remaining; // Tracks step event count for the executing planner block int32_t step_events_remaining; // Tracks step event count for the executing planner block
uint32_t d_next; // Scaled distance to next step uint32_t dist_next_step; // Scaled distance to next step
uint32_t initial_rate; // Initialized step rate at re/start of a planner block uint32_t initial_rate; // Initialized step rate at re/start of a planner block
uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute
uint32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive) uint32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
@ -154,7 +154,6 @@ void st_wake_up()
TCNT2 = 0; // Clear Timer2 TCNT2 = 0; // Clear Timer2
TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt
TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler
} }
} }
@ -184,8 +183,8 @@ void st_go_idle()
/* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based /* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based
on an inverse time stepper algorithm, where a timer ticks at a constant frequency and uses on an inverse time stepper algorithm, where a timer ticks at a constant frequency and uses
time-distance counters to track when its the approximate time for a step event. For reference, time-distance counters to track when its the approximate time for a step event. For reference,
a similar inverse-time algorithm by Pramod Ranade is susceptible to numerical round-off, a similar inverse-time algorithm by Pramod Ranade is susceptible to numerical round-off, as
meaning that some axes steps may not execute correctly for a given multi-axis motion. described, meaning that some axes steps may not execute correctly for a given multi-axis motion.
Grbl's algorithm differs by using a single inverse time-distance counter to manage a Grbl's algorithm differs by using a single inverse time-distance counter to manage a
Bresenham line algorithm for multi-axis step events, which ensures the number of steps for Bresenham line algorithm for multi-axis step events, which ensures the number of steps for
each axis are executed exactly. In other words, Grbl uses a Bresenham within a Bresenham each axis are executed exactly. In other words, Grbl uses a Bresenham within a Bresenham
@ -208,6 +207,8 @@ void st_go_idle()
with time. This means we do not have to compute them via expensive floating point beforehand. with time. This means we do not have to compute them via expensive floating point beforehand.
- Need to do an analysis to determine if these counters are really that much cheaper. At least - Need to do an analysis to determine if these counters are really that much cheaper. At least
find out when it isn't anymore. Particularly when the ISR is at a very high frequency. find out when it isn't anymore. Particularly when the ISR is at a very high frequency.
- Create NOTE: to describe that the total time in this ISR must be less than the ISR frequency
in its worst case scenario.
*/ */
ISR(TIMER2_COMPA_vect) ISR(TIMER2_COMPA_vect)
{ {
@ -233,57 +234,46 @@ ISR(TIMER2_COMPA_vect)
// Anything in the buffer? If so, load and initialize next step segment. // Anything in the buffer? If so, load and initialize next step segment.
if (segment_buffer_head != segment_buffer_tail) { if (segment_buffer_head != segment_buffer_tail) {
// NOTE: Loads after a step event. At high rates above 1/2 ISR frequency, there is // Initialize new step segment and load number of steps to execute
// a small chance that this will load at the same time as a step event. Hopefully,
// the overhead for this loading event isn't too much.. possibly 2-5 usec.
// NOTE: The stepper algorithm must control the planner buffer tail as it completes
// the block moves. Otherwise, a feed hold can leave a few step buffer line moves
// without the correct planner block information.
st_current_segment = &segment_buffer[segment_buffer_tail]; st_current_segment = &segment_buffer[segment_buffer_tail];
// Load number of steps to execute from stepper buffer
st.segment_steps_remaining = st_current_segment->n_step; st.segment_steps_remaining = st_current_segment->n_step;
// Check if the counters need to be reset for a new planner block // If the new segment starts a new planner block, initialize stepper variables and counters.
// NOTE: For new segments only, the step counters are not updated to ensure step phasing is continuous.
if (st.load_flag == LOAD_BLOCK) { if (st.load_flag == LOAD_BLOCK) {
pl_current_block = plan_get_current_block(); // Should always be there. Stepper buffer handles this. pl_current_block = plan_get_current_block(); // Should always be there. Stepper buffer handles this.
st_current_data = &segment_data[segment_buffer[segment_buffer_tail].st_data_index]; //st_current_segment->st_data_index]; st_current_data = &segment_data[segment_buffer[segment_buffer_tail].st_data_index];
// Initialize direction bits for block // Initialize direction bits for block. Set execute flag to set directions bits upon next ISR tick.
st.out_bits = pl_current_block->direction_bits ^ settings.invert_mask; st.out_bits = pl_current_block->direction_bits ^ settings.invert_mask;
st.execute_step = true; // Set flag to set direction bits upon next ISR tick. st.execute_step = true;
// Initialize Bresenham line counters // Initialize Bresenham line counters
st.counter_x = (pl_current_block->step_event_count >> 1); st.counter_x = (pl_current_block->step_event_count >> 1);
st.counter_y = st.counter_x; st.counter_y = st.counter_x;
st.counter_z = st.counter_x; st.counter_z = st.counter_x;
// Initialize inverse time and step rate counter data // Initialize inverse time, step rate data, and acceleration ramp counters
st.counter_d = st_current_data->d_next; // d_next always greater than delta_d. st.counter_dist = st_current_data->dist_next_step; // dist_next_step always greater than ramp_rate.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_per_tick = MINIMUM_STEP_RATE; } st.ramp_rate = st_current_data->initial_rate;
else { st.d_per_tick = st.delta_d; } st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK/2; // Initialize ramp counter via midpoint rule
// During feed hold, do not update rate, ramp type, or ramp counters. Keep decelerating.
// if (sys.state == STATE_CYCLE) {
st.delta_d = st_current_data->initial_rate;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Initialize ramp counter via midpoint rule
st.ramp_type = RAMP_NOOP_CRUISE; // Initialize as no ramp operation. Corrected later if necessary. st.ramp_type = RAMP_NOOP_CRUISE; // Initialize as no ramp operation. Corrected later if necessary.
// }
// Ensure the initial step rate exceeds the MINIMUM_STEP_RATE.
if (st.ramp_rate < MINIMUM_STEP_RATE) { st.dist_per_tick = MINIMUM_STEP_RATE; }
else { st.dist_per_tick = st.ramp_rate; }
} }
// Acceleration and cruise handled by ramping. Just check if deceleration needs to begin. // Check if ramp conditions have changed. If so, update ramp counters and control variables.
if ( st_current_segment->flag & (SEGMENT_DECEL | SEGMENT_ACCEL) ) { if ( st_current_segment->flag & (SEGMENT_DECEL | SEGMENT_ACCEL) ) {
/* Compute correct ramp count for a ramp change. Upon a switch from acceleration to deceleration, /* Compute correct ramp count for a ramp change. Upon a switch from acceleration to deceleration,
or vice-versa, the new ramp count must be set to trigger the next acceleration tick equal to or vice-versa, the new ramp count must be set to trigger the next acceleration tick equal to
the number of ramp ISR ticks counted since the last acceleration tick. This is ensures the the number of ramp ISR ticks counted since the last acceleration tick. This is ensures the
ramp is executed exactly as the plan dictates. Otherwise, when a ramp begins from a known ramp is executed exactly as the plan dictates. Otherwise, when a ramp begins from a known
rate (nominal/cruise or initial), the ramp count must be set to ISR_TICKS_PER_ACCELERATION_TICK/2 rate (nominal/cruise or initial), the ramp count must be set to ISR_TICKS_PER_ACCELERATION_TICK/2
as mandated by the mid-point rule. For these conditions, the ramp count have been initialized as mandated by the mid-point rule. For the latter conditions, the ramp count have been
such that the following computation is still correct. */ initialized such that the following computation is still correct. */
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK-st.ramp_count; st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK-st.counter_ramp;
if ( st_current_segment->flag & SEGMENT_DECEL ) { st.ramp_type = RAMP_DECEL; } if ( st_current_segment->flag & SEGMENT_DECEL ) { st.ramp_type = RAMP_DECEL; }
else { st.ramp_type = RAMP_ACCEL; } else { st.ramp_type = RAMP_ACCEL; }
} }
@ -300,46 +290,36 @@ ISR(TIMER2_COMPA_vect)
} }
// Adjust inverse time counter for ac/de-celerations // Adjust inverse time counter for ac/de-celerations
// NOTE: Accelerations are handled by the stepper algorithm as it's thought to be more computationally if (st.ramp_type) { // Ignored when ramp type is RAMP_NOOP_CRUISE
// efficient on the Arduino AVR. This could may not be true with higher ISR frequencies or faster CPUs. st.counter_ramp--; // Tick acceleration ramp counter
if (st.ramp_type) { // Ignored when ramp type is NOOP_CRUISE if (st.counter_ramp == 0) { // Adjust step rate when its time
st.ramp_count--; // Tick acceleration ramp counter st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
if (st.ramp_count == 0) { // Adjust step rate when its time
if (st.ramp_type == RAMP_ACCEL) { // Adjust velocity for acceleration if (st.ramp_type == RAMP_ACCEL) { // Adjust velocity for acceleration
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter st.ramp_rate += st_current_data->rate_delta;
st.delta_d += st_current_data->rate_delta; if (st.ramp_rate >= st_current_data->nominal_rate) { // Reached nominal rate.
if (st.delta_d >= st_current_data->nominal_rate) { // Reached nominal rate. st.ramp_rate = st_current_data->nominal_rate; // Set cruising velocity
st.delta_d = st_current_data->nominal_rate; // Set cruising velocity
st.ramp_type = RAMP_NOOP_CRUISE; // Set ramp flag to cruising st.ramp_type = RAMP_NOOP_CRUISE; // Set ramp flag to cruising
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Re-initialize counter for next ramp. st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK/2; // Re-initialize counter for next ramp change.
} }
} else { // Adjust velocity for deceleration. } else { // Adjust velocity for deceleration.
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter if (st.ramp_rate > st_current_data->rate_delta) {
if (st.delta_d > st_current_data->rate_delta) { st.ramp_rate -= st_current_data->rate_delta;
st.delta_d -= st_current_data->rate_delta;
} else { // Moving near zero feed rate. Gracefully slow down. } else { // Moving near zero feed rate. Gracefully slow down.
st.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow. st.ramp_rate >>= 1; // Integer divide by 2 until complete. Also prevents overflow.
// TODO: Check for and handle feed hold exit? At this point, machine is stopped.
// - Set system flag to recompute plan and reset segment buffer.
// - Segment steps in buffer needs to be returned to planner correctly.
// busy = false;
// return;
} }
} }
// Finalize adjusted step rate. Ensure minimum. // Adjust for minimum step rate, but retain operating ramp rate for accurate velocity tracing.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_per_tick = MINIMUM_STEP_RATE; } if (st.ramp_rate < MINIMUM_STEP_RATE) { st.dist_per_tick = MINIMUM_STEP_RATE; }
else { st.d_per_tick = st.delta_d; } else { st.dist_per_tick = st.ramp_rate; }
} }
} }
// Iterate inverse time counter. Triggers each Bresenham step event. // Iterate inverse time counter. Triggers each Bresenham step event.
st.counter_d -= st.d_per_tick; st.counter_dist -= st.dist_per_tick;
// Execute Bresenham step event, when it's time to do so. // Execute Bresenham step event, when it's time to do so.
if (st.counter_d < 0) { if (st.counter_dist < 0) {
st.counter_d += st_current_data->d_next; // Reload inverse time counter st.counter_dist += st_current_data->dist_next_step; // Reload inverse time counter
st.out_bits = pl_current_block->direction_bits; // Reset out_bits and reload direction bits st.out_bits = pl_current_block->direction_bits; // Reset out_bits and reload direction bits
st.execute_step = true; st.execute_step = true;
@ -349,7 +329,6 @@ ISR(TIMER2_COMPA_vect)
if (st.counter_x < 0) { if (st.counter_x < 0) {
st.out_bits |= (1<<X_STEP_BIT); st.out_bits |= (1<<X_STEP_BIT);
st.counter_x += pl_current_block->step_event_count; st.counter_x += pl_current_block->step_event_count;
// st.steps_x++;
if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; } if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; } else { sys.position[X_AXIS]++; }
} }
@ -357,7 +336,6 @@ ISR(TIMER2_COMPA_vect)
if (st.counter_y < 0) { if (st.counter_y < 0) {
st.out_bits |= (1<<Y_STEP_BIT); st.out_bits |= (1<<Y_STEP_BIT);
st.counter_y += pl_current_block->step_event_count; st.counter_y += pl_current_block->step_event_count;
// st.steps_y++;
if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; } if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; } else { sys.position[Y_AXIS]++; }
} }
@ -365,7 +343,6 @@ ISR(TIMER2_COMPA_vect)
if (st.counter_z < 0) { if (st.counter_z < 0) {
st.out_bits |= (1<<Z_STEP_BIT); st.out_bits |= (1<<Z_STEP_BIT);
st.counter_z += pl_current_block->step_event_count; st.counter_z += pl_current_block->step_event_count;
// st.steps_z++;
if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; } if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; } else { sys.position[Z_AXIS]++; }
} }
@ -373,34 +350,6 @@ ISR(TIMER2_COMPA_vect)
// Check step events for trapezoid change or end of block. // Check step events for trapezoid change or end of block.
st.segment_steps_remaining--; // Decrement step events count st.segment_steps_remaining--; // Decrement step events count
if (st.segment_steps_remaining == 0) { if (st.segment_steps_remaining == 0) {
/*
NOTE: sys.position updates could be done here. The bresenham counters can have
their own fast 8-bit addition-only counters. Here we would check the direction and
apply it to sys.position accordingly. However, this could take too much time
combined with loading a new segment during next cycle too.
TODO: Measure the time it would take in the worst case. It could still be faster
overall during segment execution if uint8 step counters tracked this and was added
to the system position variables here. Compared to worst case now, it wouldn't be
that much different.
// TODO: Upon loading, step counters would need to be zeroed.
// TODO: For feedrate overrides, we will have to execute add these values.. although
// for probing, this breaks. Current values won't be correct, unless we query it.
// It makes things more complicated, but still manageable.
if (st.steps_x > 0) {
if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS] += st.steps_x; }
else { sys.position[X_AXIS] -= st.steps_x; }
}
if (st.steps_y > 0) {
if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS] += st.steps_y; }
else { sys.position[Y_AXIS] -= st.steps_y; }
}
if (st.steps_z > 0) {
if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS] += st.steps_z; }
else { sys.position[Z_AXIS] -= st.steps_z; }
}
*/
// Line move is complete, set load line flag to check for new move. // Line move is complete, set load line flag to check for new move.
// Check if last line move in planner block. Discard if so. // Check if last line move in planner block. Discard if so.
if (st_current_segment->flag & SEGMENT_END_OF_BLOCK) { if (st_current_segment->flag & SEGMENT_END_OF_BLOCK) {
@ -412,7 +361,6 @@ ISR(TIMER2_COMPA_vect)
// Discard current segment by advancing buffer tail index // Discard current segment by advancing buffer tail index
if ( ++segment_buffer_tail == SEGMENT_BUFFER_SIZE) { segment_buffer_tail = 0; } if ( ++segment_buffer_tail == SEGMENT_BUFFER_SIZE) { segment_buffer_tail = 0; }
} }
st.out_bits ^= settings.invert_mask; // Apply step port invert mask st.out_bits ^= settings.invert_mask; // Apply step port invert mask
@ -486,6 +434,7 @@ void st_cycle_start()
{ {
if (sys.state == STATE_QUEUED) { if (sys.state == STATE_QUEUED) {
sys.state = STATE_CYCLE; sys.state = STATE_CYCLE;
st_prep_buffer(); // Initialize step segment buffer before beginning cycle.
st_wake_up(); st_wake_up();
} }
} }
@ -520,8 +469,8 @@ void st_cycle_reinitialize()
// plan_cycle_reinitialize(st_current_data->step_events_remaining); // plan_cycle_reinitialize(st_current_data->step_events_remaining);
// st.ramp_type = RAMP_ACCEL; // st.ramp_type = RAMP_ACCEL;
// st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK/2;
// st.delta_d = 0; // st.ramp_rate = 0;
// sys.state = STATE_QUEUED; // sys.state = STATE_QUEUED;
// } else { // } else {
// sys.state = STATE_IDLE; // sys.state = STATE_IDLE;
@ -580,6 +529,7 @@ void st_cycle_reinitialize()
*/ */
void st_prep_buffer() void st_prep_buffer()
{ {
if (sys.state != STATE_QUEUED) { // Block until a motion state is issued
while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer. while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer.
// Initialize new segment // Initialize new segment
@ -605,7 +555,7 @@ void st_prep_buffer()
st_prep_data->step_events_remaining = last_st_prep_data->step_events_remaining; st_prep_data->step_events_remaining = last_st_prep_data->step_events_remaining;
st_prep_data->rate_delta = last_st_prep_data->rate_delta; st_prep_data->rate_delta = last_st_prep_data->rate_delta;
st_prep_data->d_next = last_st_prep_data->d_next; st_prep_data->dist_next_step = last_st_prep_data->dist_next_step;
st_prep_data->nominal_rate = last_st_prep_data->nominal_rate; // TODO: Feedrate overrides recomputes this. st_prep_data->nominal_rate = last_st_prep_data->nominal_rate; // TODO: Feedrate overrides recomputes this.
st_prep_data->mm_per_step = last_st_prep_data->mm_per_step; st_prep_data->mm_per_step = last_st_prep_data->mm_per_step;
@ -625,7 +575,7 @@ void st_prep_buffer()
st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic) st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
st_prep_data->rate_delta = ceil(pl_prep_block->acceleration* st_prep_data->rate_delta = ceil(pl_prep_block->acceleration*
((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic) ((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic)
st_prep_data->d_next = ceil((pl_prep_block->millimeters*INV_TIME_MULTIPLIER)/pl_prep_block->step_event_count); // (mult*mm/step) st_prep_data->dist_next_step = ceil((pl_prep_block->millimeters*INV_TIME_MULTIPLIER)/pl_prep_block->step_event_count); // (mult*mm/step)
// TODO: Check if we really need to store this. // TODO: Check if we really need to store this.
st_prep_data->mm_per_step = pl_prep_block->millimeters/pl_prep_block->step_event_count; st_prep_data->mm_per_step = pl_prep_block->millimeters/pl_prep_block->step_event_count;
@ -656,7 +606,7 @@ void st_prep_buffer()
// We do this to minimize memory and computational requirements. However, this could easily be replaced with // We do this to minimize memory and computational requirements. However, this could easily be replaced with
// a more exact approximation or have a unique time per segment, if CPU and memory overhead allows. // a more exact approximation or have a unique time per segment, if CPU and memory overhead allows.
if (st_prep_data->decelerate_after <= 0) { if (st_prep_data->decelerate_after <= 0) {
if (st_prep_data->decelerate_after == 0) { prep_segment->flag = SEGMENT_DECEL; } if (st_prep_data->decelerate_after == 0) { prep_segment->flag = SEGMENT_DECEL; } // Set segment deceleration flag
else { st_prep_data->current_approx_rate -= st_prep_data->rate_delta; } else { st_prep_data->current_approx_rate -= st_prep_data->rate_delta; }
if (st_prep_data->current_approx_rate < st_prep_data->rate_delta) { st_prep_data->current_approx_rate >>= 1; } if (st_prep_data->current_approx_rate < st_prep_data->rate_delta) { st_prep_data->current_approx_rate >>= 1; }
} else { } else {
@ -669,9 +619,9 @@ void st_prep_buffer()
} }
// Compute the number of steps in the prepped segment based on the approximate current rate. // Compute the number of steps in the prepped segment based on the approximate current rate.
// NOTE: The d_next divide cancels out the INV_TIME_MULTIPLIER and converts the rate value to steps. // NOTE: The dist_next_step divide cancels out the INV_TIME_MULTIPLIER and converts the rate value to steps.
prep_segment->n_step = ceil(max(MINIMUM_STEP_RATE,st_prep_data->current_approx_rate)* prep_segment->n_step = ceil(max(MINIMUM_STEP_RATE,st_prep_data->current_approx_rate)*
(ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND)/st_prep_data->d_next); (ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND)/st_prep_data->dist_next_step);
// NOTE: Ensures it moves for very slow motions, but MINIMUM_STEP_RATE should always set this too. Perhaps // NOTE: Ensures it moves for very slow motions, but MINIMUM_STEP_RATE should always set this too. Perhaps
// a compile-time check to see if MINIMUM_STEP_RATE is set high enough is all that is needed. // a compile-time check to see if MINIMUM_STEP_RATE is set high enough is all that is needed.
prep_segment->n_step = max(prep_segment->n_step,MINIMUM_STEPS_PER_SEGMENT); prep_segment->n_step = max(prep_segment->n_step,MINIMUM_STEPS_PER_SEGMENT);
@ -697,6 +647,11 @@ void st_prep_buffer()
// Check for end of planner block // Check for end of planner block
if ( st_prep_data->step_events_remaining == 0 ) { if ( st_prep_data->step_events_remaining == 0 ) {
// TODO: When a feed hold ends, the step_events_remaining will also be zero, even though a block
// have partially been completed. We need to flag the stepper algorithm to indicate a stepper shutdown
// when complete, but not remove the planner block unless it truly is the end of the block (rare).
// Set EOB bitflag so stepper algorithm discards the planner block after this segment completes. // Set EOB bitflag so stepper algorithm discards the planner block after this segment completes.
prep_segment->flag |= SEGMENT_END_OF_BLOCK; prep_segment->flag |= SEGMENT_END_OF_BLOCK;
// Move planner pointer to next block and flag to load a new block for the next segment. // Move planner pointer to next block and flag to load a new block for the next segment.
@ -713,6 +668,7 @@ void st_prep_buffer()
// printString(" "); // printString(" ");
} }
}
} }
uint8_t st_get_prep_block_index() uint8_t st_get_prep_block_index()

View File

@ -19,20 +19,32 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>. along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* The timer calculations of this module informed by the 'RepRap cartesian firmware' by Zack Smith
and Philipp Tiefenbacher. */
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include "stepper.h" #include "stepper.h"
#include "config.h" #include "config.h"
#include "settings.h" #include "settings.h"
#include "planner.h" #include "planner.h"
#include "nuts_bolts.h"
// Some useful constants // Some useful constants
#define TICKS_PER_MICROSECOND (F_CPU/1000000) #define TICKS_PER_MICROSECOND (F_CPU/1000000)
#define CRUISE_RAMP 0
#define ACCEL_RAMP 1 #define RAMP_NOOP_CRUISE 0
#define DECEL_RAMP 2 #define RAMP_ACCEL 1
#define RAMP_DECEL 2
#define LOAD_NOOP 0
#define LOAD_SEGMENT 1
#define LOAD_BLOCK 2
#define SEGMENT_NOOP 0
#define SEGMENT_END_OF_BLOCK bit(0)
#define SEGMENT_ACCEL bit(1)
#define SEGMENT_DECEL bit(2)
#define MINIMUM_STEPS_PER_SEGMENT 1 // Don't change
#define SEGMENT_BUFFER_SIZE 6
// Stepper state variable. Contains running data and trapezoid variables. // Stepper state variable. Contains running data and trapezoid variables.
typedef struct { typedef struct {
@ -40,44 +52,85 @@ typedef struct {
int32_t counter_x, // Counter variables for the bresenham line tracer int32_t counter_x, // Counter variables for the bresenham line tracer
counter_y, counter_y,
counter_z; counter_z;
int32_t event_count; // Total event count. Retained for feed holds. uint8_t segment_steps_remaining; // Steps remaining in line segment motion
int32_t step_events_remaining; // Steps remaining in motion
// Used by Pramod Ranade inverse time algorithm // Used by inverse time algorithm to track step rate
int32_t delta_d; // Ranade distance traveled per interrupt tick int32_t counter_d; // Inverse time distance traveled since last step event
int32_t d_counter; // Ranade distance traveled since last step event uint32_t delta_d; // Inverse time distance traveled per interrupt tick
uint8_t ramp_count; // Acceleration interrupt tick counter. uint32_t d_per_tick;
uint8_t ramp_type; // Ramp type variable.
// Used by the stepper driver interrupt
uint8_t execute_step; // Flags step execution for each interrupt. uint8_t execute_step; // Flags step execution for each interrupt.
uint8_t step_pulse_time; // Step pulse reset time after step rise
uint8_t out_bits; // The next stepping-bits to be output
uint8_t load_flag;
uint8_t ramp_count;
uint8_t ramp_type;
} stepper_t; } stepper_t;
static stepper_t st; static stepper_t st;
static block_t *current_block; // A pointer to the block currently being traced
// Used by the stepper driver interrupt // Stores stepper common data for executing steps in the segment buffer. Data can change mid-block when the
static uint8_t step_pulse_time; // Step pulse reset time after step rise // planner updates the remaining block velocity profile with a more optimal plan or a feedrate override occurs.
static uint8_t out_bits; // The next stepping-bits to be output // NOTE: Normally, this buffer is partially in-use, but, for the worst case scenario, it will never exceed
// the number of accessible stepper buffer segments (SEGMENT_BUFFER_SIZE-1).
typedef struct {
int32_t step_events_remaining; // Tracks step event count for the executing planner block
uint32_t d_next; // Scaled distance to next step
uint32_t initial_rate; // Initialized step rate at re/start of a planner block
uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute
uint32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
uint32_t current_approx_rate; // Tracks the approximate segment rate to predict steps per segment to execute
int32_t decelerate_after; // Tracks when to initiate deceleration according to the planner block
float mm_per_step;
} st_data_t;
static st_data_t segment_data[SEGMENT_BUFFER_SIZE-1];
// NOTE: If the main interrupt is guaranteed to be complete before the next interrupt, then // Primary stepper segment ring buffer. Contains small, short line segments for the stepper algorithm to execute,
// this blocking variable is no longer needed. Only here for safety reasons. // which are "checked-out" incrementally from the first block in the planner buffer. Once "checked-out", the steps
static volatile uint8_t busy; // True when "Stepper Driver Interrupt" is being serviced. Used to avoid retriggering that handler. // in the segments buffer cannot be modified by the planner, where the remaining planner block steps still can.
typedef struct {
uint8_t n_step; // Number of step events to be executed for this segment
uint8_t st_data_index; // Stepper buffer common data index. Uses this information to execute this segment.
uint8_t flag; // Stepper algorithm bit-flag for special execution conditions.
} st_segment_t;
static st_segment_t segment_buffer[SEGMENT_BUFFER_SIZE];
// __________________________ // Step segment ring buffer indices
// /| |\ _________________ ^ static volatile uint8_t segment_buffer_tail;
// / | | \ /| |\ | static volatile uint8_t segment_buffer_head;
// / | | \ / | | \ s static uint8_t segment_next_head;
// / | | | | | \ p
// / | | | | | \ e
// +-----+------------------------+---+--+---------------+----+ e
// | BLOCK 1 | BLOCK 2 | d
//
// time ----->
//
// The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates by block->rate_delta
// until reaching cruising speed block->nominal_rate, and/or until step_events_remaining reaches block->decelerate_after
// after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as
// +/- block->rate_delta velocity increments by the midpoint rule at each ACCELERATION_TICKS_PER_SECOND.
static volatile uint8_t busy; // Used to avoid ISR nesting of the "Stepper Driver Interrupt". Should never occur though.
static plan_block_t *pl_current_block; // A pointer to the planner block currently being traced
static st_segment_t *st_current_segment;
static st_data_t *st_current_data;
// Pointers for the step segment being prepped from the planner buffer. Accessed only by the
// main program. Pointers may be planning segments or planner blocks ahead of what being executed.
static plan_block_t *pl_prep_block; // Pointer to the planner block being prepped
static st_data_t *st_prep_data; // Pointer to the stepper common data being prepped
static uint8_t pl_prep_index; // Index of planner block being prepped
static uint8_t st_data_prep_index; // Index of stepper common data block being prepped
static uint8_t pl_partial_block_flag; // Flag indicating the planner has modified the prepped planner block
/* __________________________
/| |\ _________________ ^
/ | | \ /| |\ |
/ | | \ / | | \ s
/ | | | | | \ p
/ | | | | | \ e
+-----+------------------------+---+--+---------------+----+ e
| BLOCK 1 | BLOCK 2 | d
time ----->
The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates by block->rate_delta
until reaching cruising speed block->nominal_rate, and/or until step_events_remaining reaches block->decelerate_after
after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as
+/- block->rate_delta velocity increments by the midpoint rule at each ACCELERATION_TICKS_PER_SECOND.
*/
// Stepper state initialization. Cycle should only start if the st.cycle_start flag is // Stepper state initialization. Cycle should only start if the st.cycle_start flag is
// enabled. Startup init and limits call this function but shouldn't start the cycle. // enabled. Startup init and limits call this function but shouldn't start the cycle.
@ -91,14 +144,17 @@ void st_wake_up()
} }
if (sys.state == STATE_CYCLE) { if (sys.state == STATE_CYCLE) {
// Initialize stepper output bits // Initialize stepper output bits
out_bits = settings.invert_mask; st.out_bits = settings.invert_mask;
// Initialize step pulse timing from settings. // Initialize step pulse timing from settings.
step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3); st.step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3);
// Enable stepper driver interrupt // Enable stepper driver interrupt
st.execute_step = false; st.execute_step = false;
st.load_flag = LOAD_BLOCK;
TCNT2 = 0; // Clear Timer2 TCNT2 = 0; // Clear Timer2
TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt
TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler
} }
} }
@ -125,22 +181,34 @@ void st_go_idle()
} }
// "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based /* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based
// on the Pramod Ranade inverse time stepper algorithm, where a timer ticks at a constant on an inverse time stepper algorithm, where a timer ticks at a constant frequency and uses
// frequency and uses time-distance counters to track when its the approximate time for any time-distance counters to track when its the approximate time for a step event. For reference,
// step event. However, the Ranade algorithm, as described, is susceptible to numerical round-off, a similar inverse-time algorithm by Pramod Ranade is susceptible to numerical round-off,
// meaning that some axes steps may not execute for a given multi-axis motion. meaning that some axes steps may not execute correctly for a given multi-axis motion.
// Grbl's algorithm slightly differs by using a single Ranade time-distance counter to manage Grbl's algorithm differs by using a single inverse time-distance counter to manage a
// a Bresenham line algorithm for multi-axis step events which ensures the number of steps for Bresenham line algorithm for multi-axis step events, which ensures the number of steps for
// each axis are executed exactly. In other words, it uses a Bresenham within a Bresenham algorithm, each axis are executed exactly. In other words, Grbl uses a Bresenham within a Bresenham
// where one tracks time(Ranade) and the other steps. algorithm, where one tracks time for step events and the other steps for multi-axis moves.
// This interrupt pops blocks from the block_buffer and executes them by pulsing the stepper pins Grbl specifically uses the Bresenham algorithm due to its innate mathematical exactness and
// appropriately. It is supported by The Stepper Port Reset Interrupt which it uses to reset the low computational overhead, requiring simple integer +,- counters only.
// stepper port after each pulse. The bresenham line tracer algorithm controls all three stepper This interrupt pops blocks from the step segment buffer and executes them by pulsing the
// outputs simultaneously with these two interrupts. stepper pins appropriately. It is supported by The Stepper Port Reset Interrupt which it uses
// to reset the stepper port after each pulse. The bresenham line tracer algorithm controls all
// NOTE: Average time in this ISR is: 5 usec iterating timers only, 20-25 usec with step event, or three stepper outputs simultaneously with these two interrupts.
// 15 usec when popping a block. So, ensure Ranade frequency and step pulse times work with this. */
/* TODO:
- Measure time in ISR. Typical and worst-case. Should be virtually identical to last algorithm.
There are no major changes to the base operations of this ISR with the new segment buffer.
- Write how the acceleration counters work and why they are set at half via mid-point rule.
- Determine if placing the position counters elsewhere (or change them to 8-bit variables that
are added to the system position counters at the end of a segment) frees up cycles.
- Write a blurb about how the acceleration should be handled within the ISR. All of the
time/step/ramp counters accurately keep track of the remainders and phasing of the variables
with time. This means we do not have to compute them via expensive floating point beforehand.
- Need to do an analysis to determine if these counters are really that much cheaper. At least
find out when it isn't anymore. Particularly when the ISR is at a very high frequency.
*/
ISR(TIMER2_COMPA_vect) ISR(TIMER2_COMPA_vect)
{ {
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR // SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
@ -150,158 +218,212 @@ ISR(TIMER2_COMPA_vect)
// before any step pulse due to algorithm design. // before any step pulse due to algorithm design.
if (st.execute_step) { if (st.execute_step) {
st.execute_step = false; st.execute_step = false;
STEPPING_PORT = ( STEPPING_PORT & ~(DIRECTION_MASK | STEP_MASK) ) | out_bits; STEPPING_PORT = ( STEPPING_PORT & ~(DIRECTION_MASK | STEP_MASK) ) | st.out_bits;
TCNT0 = step_pulse_time; // Reload Timer0 counter. TCNT0 = st.step_pulse_time; // Reload Timer0 counter.
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
} }
busy = true; busy = true;
sei(); // Re-enable interrupts. This ISR will still finish before returning to main program. sei(); // Re-enable interrupts to allow Stepper Port Reset Interrupt to fire on-time.
// NOTE: The remaining code in this ISR will finish before returning to main program.
// If there is no current block, attempt to pop one from the buffer // If there is no step segment, attempt to pop one from the stepper buffer
if (current_block == NULL) { if (st.load_flag != LOAD_NOOP) {
// Anything in the buffer? If so, initialize next motion. // Anything in the buffer? If so, load and initialize next step segment.
current_block = plan_get_current_block(); if (segment_buffer_head != segment_buffer_tail) {
if (current_block != NULL) {
// By algorithm design, the loading of the next block never coincides with a step event, // NOTE: Loads after a step event. At high rates above 1/2 ISR frequency, there is
// since there is always one Ranade timer tick before a step event occurs. This means // a small chance that this will load at the same time as a step event. Hopefully,
// that the Bresenham counter math never is performed at the same time as the loading // the overhead for this loading event isn't too much.. possibly 2-5 usec.
// of a block, hence helping minimize total time spent in this interrupt.
// NOTE: The stepper algorithm must control the planner buffer tail as it completes
// the block moves. Otherwise, a feed hold can leave a few step buffer line moves
// without the correct planner block information.
st_current_segment = &segment_buffer[segment_buffer_tail];
// Load number of steps to execute from stepper buffer
st.segment_steps_remaining = st_current_segment->n_step;
// Check if the counters need to be reset for a new planner block
if (st.load_flag == LOAD_BLOCK) {
pl_current_block = plan_get_current_block(); // Should always be there. Stepper buffer handles this.
st_current_data = &segment_data[segment_buffer[segment_buffer_tail].st_data_index]; //st_current_segment->st_data_index];
// Initialize direction bits for block // Initialize direction bits for block
out_bits = current_block->direction_bits ^ settings.invert_mask; st.out_bits = pl_current_block->direction_bits ^ settings.invert_mask;
st.execute_step = true; // Set flag to set direction bits. st.execute_step = true; // Set flag to set direction bits upon next ISR tick.
// Initialize Bresenham variables // Initialize Bresenham line counters
st.counter_x = (current_block->step_event_count >> 1); st.counter_x = (pl_current_block->step_event_count >> 1);
st.counter_y = st.counter_x; st.counter_y = st.counter_x;
st.counter_z = st.counter_x; st.counter_z = st.counter_x;
st.event_count = current_block->step_event_count;
st.step_events_remaining = st.event_count;
// During feed hold, do not update Ranade counter, rate, or ramp type. Keep decelerating. // Initialize inverse time and step rate counter data
if (sys.state == STATE_CYCLE) { st.counter_d = st_current_data->d_next; // d_next always greater than delta_d.
// Initialize Ranade variables if (st.delta_d < MINIMUM_STEP_RATE) { st.d_per_tick = MINIMUM_STEP_RATE; }
st.d_counter = current_block->d_next; else { st.d_per_tick = st.delta_d; }
st.delta_d = current_block->initial_rate;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // During feed hold, do not update rate, ramp type, or ramp counters. Keep decelerating.
// if (sys.state == STATE_CYCLE) {
st.delta_d = st_current_data->initial_rate;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Initialize ramp counter via midpoint rule
st.ramp_type = RAMP_NOOP_CRUISE; // Initialize as no ramp operation. Corrected later if necessary.
// }
// Initialize ramp type.
if (st.step_events_remaining == current_block->decelerate_after) { st.ramp_type = DECEL_RAMP; }
else if (st.delta_d == current_block->nominal_rate) { st.ramp_type = CRUISE_RAMP; }
else { st.ramp_type = ACCEL_RAMP; }
} }
// Acceleration and cruise handled by ramping. Just check if deceleration needs to begin.
if ( st_current_segment->flag & (SEGMENT_DECEL | SEGMENT_ACCEL) ) {
/* Compute correct ramp count for a ramp change. Upon a switch from acceleration to deceleration,
or vice-versa, the new ramp count must be set to trigger the next acceleration tick equal to
the number of ramp ISR ticks counted since the last acceleration tick. This is ensures the
ramp is executed exactly as the plan dictates. Otherwise, when a ramp begins from a known
rate (nominal/cruise or initial), the ramp count must be set to ISR_TICKS_PER_ACCELERATION_TICK/2
as mandated by the mid-point rule. For these conditions, the ramp count have been initialized
such that the following computation is still correct. */
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK-st.ramp_count;
if ( st_current_segment->flag & SEGMENT_DECEL ) { st.ramp_type = RAMP_DECEL; }
else { st.ramp_type = RAMP_ACCEL; }
}
st.load_flag = LOAD_NOOP; // Segment motion loaded. Set no-operation flag to skip during execution.
} else { } else {
// Can't discard planner block here if a feed hold stops in middle of block.
st_go_idle(); st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end
return; // Nothing to do but exit. return; // Nothing to do but exit.
} }
} }
// Adjust inverse time counter for ac/de-celerations // Adjust inverse time counter for ac/de-celerations
if (st.ramp_type) { // NOTE: Accelerations are handled by the stepper algorithm as it's thought to be more computationally
// Tick acceleration ramp counter // efficient on the Arduino AVR. This could may not be true with higher ISR frequencies or faster CPUs.
st.ramp_count--; if (st.ramp_type) { // Ignored when ramp type is NOOP_CRUISE
if (st.ramp_count == 0) { st.ramp_count--; // Tick acceleration ramp counter
if (st.ramp_count == 0) { // Adjust step rate when its time
if (st.ramp_type == RAMP_ACCEL) { // Adjust velocity for acceleration
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
if (st.ramp_type == ACCEL_RAMP) { // Adjust velocity for acceleration st.delta_d += st_current_data->rate_delta;
st.delta_d += current_block->rate_delta; if (st.delta_d >= st_current_data->nominal_rate) { // Reached nominal rate.
if (st.delta_d >= current_block->nominal_rate) { // Reached cruise state. st.delta_d = st_current_data->nominal_rate; // Set cruising velocity
st.ramp_type = CRUISE_RAMP; st.ramp_type = RAMP_NOOP_CRUISE; // Set ramp flag to cruising
st.delta_d = current_block->nominal_rate; // Set cruise velocity st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Re-initialize counter for next ramp.
} }
} else if (st.ramp_type == DECEL_RAMP) { // Adjust velocity for deceleration } else { // Adjust velocity for deceleration.
if (st.delta_d > current_block->rate_delta) { st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
st.delta_d -= current_block->rate_delta; if (st.delta_d > st_current_data->rate_delta) {
} else { st.delta_d -= st_current_data->rate_delta;
} else { // Moving near zero feed rate. Gracefully slow down.
st.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow. st.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow.
// TODO: Check for and handle feed hold exit? At this point, machine is stopped.
// - Set system flag to recompute plan and reset segment buffer.
// - Segment steps in buffer needs to be returned to planner correctly.
// busy = false;
// return;
} }
} }
// Finalize adjusted step rate. Ensure minimum.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_per_tick = MINIMUM_STEP_RATE; }
else { st.d_per_tick = st.delta_d; }
} }
} }
// Iterate Pramod Ranade inverse time counter. Triggers each Bresenham step event. // Iterate inverse time counter. Triggers each Bresenham step event.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_counter -= MINIMUM_STEP_RATE; } st.counter_d -= st.d_per_tick;
else { st.d_counter -= st.delta_d; }
// Execute Bresenham step event, when it's time to do so. // Execute Bresenham step event, when it's time to do so.
if (st.d_counter < 0) { if (st.counter_d < 0) {
st.d_counter += current_block->d_next; st.counter_d += st_current_data->d_next; // Reload inverse time counter
// Check for feed hold state and execute accordingly. st.out_bits = pl_current_block->direction_bits; // Reset out_bits and reload direction bits
if (sys.state == STATE_HOLD) {
if (st.ramp_type != DECEL_RAMP) {
st.ramp_type = DECEL_RAMP;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
}
if (st.delta_d <= current_block->rate_delta) {
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP);
return;
}
}
// TODO: Vary Bresenham resolution for smoother motions or enable faster step rates (>20kHz).
out_bits = current_block->direction_bits; // Reset out_bits and reload direction bits
st.execute_step = true; st.execute_step = true;
// Execute step displacement profile by Bresenham line algorithm // Execute step displacement profile by Bresenham line algorithm
st.counter_x -= current_block->steps[X_AXIS]; st.counter_x -= pl_current_block->steps[X_AXIS];
if (st.counter_x < 0) { if (st.counter_x < 0) {
out_bits |= (1<<X_STEP_BIT); st.out_bits |= (1<<X_STEP_BIT);
st.counter_x += st.event_count; st.counter_x += pl_current_block->step_event_count;
if (out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; } // st.steps_x++;
if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; } else { sys.position[X_AXIS]++; }
} }
st.counter_y -= current_block->steps[Y_AXIS]; st.counter_y -= pl_current_block->steps[Y_AXIS];
if (st.counter_y < 0) { if (st.counter_y < 0) {
out_bits |= (1<<Y_STEP_BIT); st.out_bits |= (1<<Y_STEP_BIT);
st.counter_y += st.event_count; st.counter_y += pl_current_block->step_event_count;
if (out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; } // st.steps_y++;
if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; } else { sys.position[Y_AXIS]++; }
} }
st.counter_z -= current_block->steps[Z_AXIS]; st.counter_z -= pl_current_block->steps[Z_AXIS];
if (st.counter_z < 0) { if (st.counter_z < 0) {
out_bits |= (1<<Z_STEP_BIT); st.out_bits |= (1<<Z_STEP_BIT);
st.counter_z += st.event_count; st.counter_z += pl_current_block->step_event_count;
if (out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; } // st.steps_z++;
if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; } else { sys.position[Z_AXIS]++; }
} }
// Check step events for trapezoid change or end of block. // Check step events for trapezoid change or end of block.
st.step_events_remaining--; // Decrement step events count st.segment_steps_remaining--; // Decrement step events count
if (st.step_events_remaining) { if (st.segment_steps_remaining == 0) {
if (st.ramp_type != DECEL_RAMP) { /*
// Acceleration and cruise handled by ramping. Just check for deceleration. NOTE: sys.position updates could be done here. The bresenham counters can have
if (st.step_events_remaining <= current_block->decelerate_after) { their own fast 8-bit addition-only counters. Here we would check the direction and
st.ramp_type = DECEL_RAMP; apply it to sys.position accordingly. However, this could take too much time
if (st.step_events_remaining == current_block->decelerate_after) { combined with loading a new segment during next cycle too.
if (st.delta_d == current_block->nominal_rate) { TODO: Measure the time it would take in the worst case. It could still be faster
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Set ramp counter for trapezoid overall during segment execution if uint8 step counters tracked this and was added
} else { to the system position variables here. Compared to worst case now, it wouldn't be
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK-st.ramp_count; // Set ramp counter for triangle that much different.
// TODO: Upon loading, step counters would need to be zeroed.
// TODO: For feedrate overrides, we will have to execute add these values.. although
// for probing, this breaks. Current values won't be correct, unless we query it.
// It makes things more complicated, but still manageable.
if (st.steps_x > 0) {
if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS] += st.steps_x; }
else { sys.position[X_AXIS] -= st.steps_x; }
} }
if (st.steps_y > 0) {
if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS] += st.steps_y; }
else { sys.position[Y_AXIS] -= st.steps_y; }
} }
if (st.steps_z > 0) {
if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS] += st.steps_z; }
else { sys.position[Z_AXIS] -= st.steps_z; }
} }
} */
} else {
// If current block is finished, reset pointer // Line move is complete, set load line flag to check for new move.
current_block = NULL; // Check if last line move in planner block. Discard if so.
if (st_current_segment->flag & SEGMENT_END_OF_BLOCK) {
plan_discard_current_block(); plan_discard_current_block();
st.load_flag = LOAD_BLOCK;
} else {
st.load_flag = LOAD_SEGMENT;
} }
out_bits ^= settings.invert_mask; // Apply step port invert mask // Discard current segment by advancing buffer tail index
if ( ++segment_buffer_tail == SEGMENT_BUFFER_SIZE) { segment_buffer_tail = 0; }
}
st.out_bits ^= settings.invert_mask; // Apply step port invert mask
} }
busy = false; busy = false;
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT;
} }
// The Stepper Port Reset Interrupt: Timer0 OVF interrupt handles the falling edge of the // The Stepper Port Reset Interrupt: Timer0 OVF interrupt handles the falling edge of the step
// step pulse. This should always trigger before the next Timer2 COMPA interrupt and independently // pulse. This should always trigger before the next Timer2 COMPA interrupt and independently
// finish, if Timer2 is disabled after completing a move. // finish, if Timer2 is disabled after completing a move.
ISR(TIMER0_OVF_vect) ISR(TIMER0_OVF_vect)
{ {
@ -314,8 +436,20 @@ ISR(TIMER0_OVF_vect)
void st_reset() void st_reset()
{ {
memset(&st, 0, sizeof(st)); memset(&st, 0, sizeof(st));
current_block = NULL;
st.load_flag = LOAD_BLOCK;
busy = false; busy = false;
pl_current_block = NULL; // Planner block pointer used by stepper algorithm
pl_prep_block = NULL; // Planner block pointer used by segment buffer
pl_prep_index = 0; // Planner buffer indices are also reset to zero.
st_data_prep_index = 0;
segment_buffer_tail = 0;
segment_buffer_head = 0; // empty = tail
segment_next_head = 1;
pl_partial_block_flag = false;
} }
@ -374,14 +508,239 @@ void st_feed_hold()
// Only the planner de/ac-celerations profiles and stepper rates have been updated. // Only the planner de/ac-celerations profiles and stepper rates have been updated.
void st_cycle_reinitialize() void st_cycle_reinitialize()
{ {
if (current_block != NULL) { // if (pl_current_block != NULL) {
// Replan buffer from the feed hold stop location. // Replan buffer from the feed hold stop location.
plan_cycle_reinitialize(st.step_events_remaining);
st.ramp_type = ACCEL_RAMP; // TODO: Need to add up all of the step events in the current planner block to give
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // back to the planner. Should only need it for the current block.
st.delta_d = 0; // BUT! The planner block millimeters is all changed and may be changed into the next
sys.state = STATE_QUEUED; // planner block. The block millimeters would need to be recalculated via step counts
} else { // and the mm/step variable.
// OR. Do we plan the feed hold itself down with the planner.
// plan_cycle_reinitialize(st_current_data->step_events_remaining);
// st.ramp_type = RAMP_ACCEL;
// st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
// st.delta_d = 0;
// sys.state = STATE_QUEUED;
// } else {
// sys.state = STATE_IDLE;
// }
sys.state = STATE_IDLE; sys.state = STATE_IDLE;
}
/* Prepares step segment buffer. Continuously called from main program.
The segment buffer is an intermediary buffer interface between the execution of steps
by the stepper algorithm and the velocity profiles generated by the planner. The stepper
algorithm only executes steps within the segment buffer and is filled by the main program
when steps are "checked-out" from the first block in the planner buffer. This keeps the
step execution and planning optimization processes atomic and protected from each other.
The number of steps "checked-out" from the planner buffer and the number of segments in
the segment buffer is sized and computed such that no operation in the main program takes
longer than the time it takes the stepper algorithm to empty it before refilling it.
Currently, the segment buffer conservatively holds roughly up to 40-60 msec of steps.
NOTE: The segment buffer executes a set number of steps over an approximate time period.
If we try to execute over a set time period, it is difficult to guarantee or predict how
many steps will execute over it, especially when the step pulse phasing between the
neighboring segments are kept consistent. Meaning that, if the last segment step pulses
right before its end, the next segment must delay its first pulse so that the step pulses
are consistently spaced apart over time to keep the step pulse train nice and smooth.
Keeping track of phasing and ensuring that the exact number of steps are executed as
defined by the planner block, the related computational overhead gets quickly and
prohibitively expensive, especially in real-time.
Since the stepper algorithm automatically takes care of the step pulse phasing with
its ramp and inverse time counters, we don't have to explicitly and expensively track the
exact number of steps, time, or phasing of steps. All we need to do is approximate
the number of steps in each segment such that the segment buffer has enough execution time
for the main program to do what it needs to do and refill it when it has time. In other
words, we just need to compute a cheap approximation of the current velocity and the
number of steps over it.
*/
/*
TODO: Figure out how to enforce a deceleration when a feedrate override is reduced.
The problem is that when an override is reduced, the planner may not plan back to
the current rate. Meaning that the velocity profiles for certain conditions no longer
are trapezoidal or triangular. For example, if the current block is cruising at a
nominal rate and the feedrate override is reduced, the new nominal rate will now be
lower. The velocity profile must first decelerate to the new nominal rate and then
follow on the new plan. So the remaining velocity profile will have a decelerate,
cruise, and another decelerate.
Another issue is whether or not a feedrate override reduction causes a deceleration
that acts over several planner blocks. For example, say that the plan is already
heavily decelerating throughout it, reducing the feedrate will not do much to it. So,
how do we determine when to resume the new plan? How many blocks do we have to wait
until the new plan intersects with the deceleration curve? One plus though, the
deceleration will never be more than the number of blocks in the entire planner buffer,
but it theoretically can be equal to it when all planner blocks are decelerating already.
*/
void st_prep_buffer()
{
while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer.
// Initialize new segment
st_segment_t *prep_segment = &segment_buffer[segment_buffer_head];
prep_segment->flag = SEGMENT_NOOP;
// Determine if we need to load a new planner block.
if (pl_prep_block == NULL) {
pl_prep_block = plan_get_block_by_index(pl_prep_index); // Query planner for a queued block
if (pl_prep_block == NULL) { return; } // No planner blocks. Exit.
// Increment stepper common data index
if ( ++st_data_prep_index == (SEGMENT_BUFFER_SIZE-1) ) { st_data_prep_index = 0; }
// Check if the planner has re-computed this block mid-execution. If so, push the previous segment
// data. Otherwise, prepare a new segment data for the new planner block.
if (pl_partial_block_flag) {
// Prepare new shared segment block data and copy the relevant last segment block data.
st_data_t *last_st_prep_data;
last_st_prep_data = st_prep_data;
st_prep_data = &segment_data[st_data_prep_index];
st_prep_data->step_events_remaining = last_st_prep_data->step_events_remaining;
st_prep_data->rate_delta = last_st_prep_data->rate_delta;
st_prep_data->d_next = last_st_prep_data->d_next;
st_prep_data->nominal_rate = last_st_prep_data->nominal_rate; // TODO: Feedrate overrides recomputes this.
st_prep_data->mm_per_step = last_st_prep_data->mm_per_step;
pl_partial_block_flag = false; // Reset flag
} else {
// Prepare commonly shared planner block data for the ensuing segment buffer moves ad-hoc, since
// the planner buffer can dynamically change the velocity profile data as blocks are added.
st_prep_data = &segment_data[st_data_prep_index];
// Initialize Bresenham variables
st_prep_data->step_events_remaining = pl_prep_block->step_event_count;
// Convert planner block velocity profile data to stepper rate and step distance data.
st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
st_prep_data->rate_delta = ceil(pl_prep_block->acceleration*
((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic)
st_prep_data->d_next = ceil((pl_prep_block->millimeters*INV_TIME_MULTIPLIER)/pl_prep_block->step_event_count); // (mult*mm/step)
// TODO: Check if we really need to store this.
st_prep_data->mm_per_step = pl_prep_block->millimeters/pl_prep_block->step_event_count;
}
// Convert planner entry speed to stepper initial rate.
st_prep_data->initial_rate = ceil(sqrt(pl_prep_block->entry_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
// TODO: Nominal rate changes with feedrate override.
// st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
st_prep_data->current_approx_rate = st_prep_data->initial_rate;
// Calculate the planner block velocity profile type, determine deceleration point, and initial ramp.
float mm_decelerate_after = plan_calculate_velocity_profile(pl_prep_index);
st_prep_data->decelerate_after = ceil( mm_decelerate_after/st_prep_data->mm_per_step );
if (st_prep_data->decelerate_after > 0) { // If 0, SEGMENT_DECEL flag is set later.
if (st_prep_data->initial_rate != st_prep_data->nominal_rate) { prep_segment->flag = SEGMENT_ACCEL; }
}
}
// Set new segment to point to the current segment data block.
prep_segment->st_data_index = st_data_prep_index;
// Approximate the velocity over the new segment using the already computed rate values.
// NOTE: This assumes that each segment will have an execution time roughly equal to every ACCELERATION_TICK.
// We do this to minimize memory and computational requirements. However, this could easily be replaced with
// a more exact approximation or have a unique time per segment, if CPU and memory overhead allows.
if (st_prep_data->decelerate_after <= 0) {
if (st_prep_data->decelerate_after == 0) { prep_segment->flag = SEGMENT_DECEL; } // Set segment deceleration flag
else { st_prep_data->current_approx_rate -= st_prep_data->rate_delta; }
if (st_prep_data->current_approx_rate < st_prep_data->rate_delta) { st_prep_data->current_approx_rate >>= 1; }
} else {
if (st_prep_data->current_approx_rate < st_prep_data->nominal_rate) {
st_prep_data->current_approx_rate += st_prep_data->rate_delta;
if (st_prep_data->current_approx_rate > st_prep_data->nominal_rate) {
st_prep_data->current_approx_rate = st_prep_data->nominal_rate;
}
}
}
// Compute the number of steps in the prepped segment based on the approximate current rate.
// NOTE: The d_next divide cancels out the INV_TIME_MULTIPLIER and converts the rate value to steps.
prep_segment->n_step = ceil(max(MINIMUM_STEP_RATE,st_prep_data->current_approx_rate)*
(ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND)/st_prep_data->d_next);
// NOTE: Ensures it moves for very slow motions, but MINIMUM_STEP_RATE should always set this too. Perhaps
// a compile-time check to see if MINIMUM_STEP_RATE is set high enough is all that is needed.
prep_segment->n_step = max(prep_segment->n_step,MINIMUM_STEPS_PER_SEGMENT);
// NOTE: As long as the ACCELERATION_TICKS_PER_SECOND is valid, n_step should never exceed 255 and overflow.
// prep_segment->n_step = min(prep_segment->n_step,MAXIMUM_STEPS_PER_BLOCK); // Prevent unsigned int8 overflow.
// Check if n_step exceeds steps remaining in planner block. If so, truncate.
if (prep_segment->n_step > st_prep_data->step_events_remaining) {
prep_segment->n_step = st_prep_data->step_events_remaining;
}
// Check if n_step crosses decelerate point in block. If so, truncate to ensure the deceleration
// ramp counters are set correctly during execution.
if (st_prep_data->decelerate_after > 0) {
if (prep_segment->n_step > st_prep_data->decelerate_after) {
prep_segment->n_step = st_prep_data->decelerate_after;
}
}
// Update stepper common data variables.
st_prep_data->decelerate_after -= prep_segment->n_step;
st_prep_data->step_events_remaining -= prep_segment->n_step;
// Check for end of planner block
if ( st_prep_data->step_events_remaining == 0 ) {
// Set EOB bitflag so stepper algorithm discards the planner block after this segment completes.
prep_segment->flag |= SEGMENT_END_OF_BLOCK;
// Move planner pointer to next block and flag to load a new block for the next segment.
pl_prep_index = plan_next_block_index(pl_prep_index);
pl_prep_block = NULL;
}
// New step segment completed. Increment segment buffer indices.
segment_buffer_head = segment_next_head;
if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { segment_next_head = 0; }
// long a = prep_segment->n_step;
// printInteger(a);
// printString(" ");
} }
} }
uint8_t st_get_prep_block_index()
{
// Returns only the index but doesn't state if the block has been partially executed. How do we simply check for this?
return(pl_prep_index);
}
void st_fetch_partial_block_parameters(uint8_t block_index, float *millimeters_remaining, uint8_t *is_decelerating)
{
// if called, can we assume that this always changes and needs to be updated? if so, then
// we can perform all of the segment buffer setup tasks here to make sure the next time
// the segments are loaded, the st_data buffer is updated correctly.
// !!! Make sure that this is always pointing to the correct st_prep_data block.
// When a mid-block acceleration occurs, we have to make sure the ramp counters are updated
// correctly, much in the same fashion as the deceleration counters. Need to think about this
// make sure this is right, but i'm pretty sure it is.
// TODO: NULL means that the segment buffer has just completed a planner block. Clean up!
if (pl_prep_block != NULL) {
*millimeters_remaining = st_prep_data->step_events_remaining*st_prep_data->mm_per_step;
if (st_prep_data->decelerate_after > 0) { *is_decelerating = false; }
else { *is_decelerating = true; }
// Flag for new prep_block when st_prep_buffer() is called after the planner recomputes.
pl_partial_block_flag = true;
pl_prep_block = NULL;
}
return;
}

387
stepper_v0_9.c Normal file
View File

@ -0,0 +1,387 @@
/*
stepper.c - stepper motor driver: executes motion plans using stepper motors
Part of Grbl
Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
/* The timer calculations of this module informed by the 'RepRap cartesian firmware' by Zack Smith
and Philipp Tiefenbacher. */
#include <avr/interrupt.h>
#include "stepper.h"
#include "config.h"
#include "settings.h"
#include "planner.h"
// Some useful constants
#define TICKS_PER_MICROSECOND (F_CPU/1000000)
#define CRUISE_RAMP 0
#define ACCEL_RAMP 1
#define DECEL_RAMP 2
// Stepper state variable. Contains running data and trapezoid variables.
typedef struct {
// Used by the bresenham line algorithm
int32_t counter_x, // Counter variables for the bresenham line tracer
counter_y,
counter_z;
int32_t event_count; // Total event count. Retained for feed holds.
int32_t step_events_remaining; // Steps remaining in motion
// Used by Pramod Ranade inverse time algorithm
int32_t delta_d; // Ranade distance traveled per interrupt tick
int32_t d_counter; // Ranade distance traveled since last step event
uint8_t ramp_count; // Acceleration interrupt tick counter.
uint8_t ramp_type; // Ramp type variable.
uint8_t execute_step; // Flags step execution for each interrupt.
} stepper_t;
static stepper_t st;
static block_t *current_block; // A pointer to the block currently being traced
// Used by the stepper driver interrupt
static uint8_t step_pulse_time; // Step pulse reset time after step rise
static uint8_t out_bits; // The next stepping-bits to be output
// NOTE: If the main interrupt is guaranteed to be complete before the next interrupt, then
// this blocking variable is no longer needed. Only here for safety reasons.
static volatile uint8_t busy; // True when "Stepper Driver Interrupt" is being serviced. Used to avoid retriggering that handler.
// __________________________
// /| |\ _________________ ^
// / | | \ /| |\ |
// / | | \ / | | \ s
// / | | | | | \ p
// / | | | | | \ e
// +-----+------------------------+---+--+---------------+----+ e
// | BLOCK 1 | BLOCK 2 | d
//
// time ----->
//
// The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates by block->rate_delta
// until reaching cruising speed block->nominal_rate, and/or until step_events_remaining reaches block->decelerate_after
// after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as
// +/- block->rate_delta velocity increments by the midpoint rule at each ACCELERATION_TICKS_PER_SECOND.
// Stepper state initialization. Cycle should only start if the st.cycle_start flag is
// enabled. Startup init and limits call this function but shouldn't start the cycle.
void st_wake_up()
{
// Enable steppers by resetting the stepper disable port
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) {
STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT);
} else {
STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
}
if (sys.state == STATE_CYCLE) {
// Initialize stepper output bits
out_bits = settings.invert_mask;
// Initialize step pulse timing from settings.
step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3);
// Enable stepper driver interrupt
st.execute_step = false;
TCNT2 = 0; // Clear Timer2
TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt
TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler
}
}
// Stepper shutdown
void st_go_idle()
{
// Disable stepper driver interrupt. Allow Timer0 to finish. It will disable itself.
TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt
TCCR2B = 0; // Disable Timer2
busy = false;
// Disable steppers only upon system alarm activated or by user setting to not be kept enabled.
if ((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) {
// Force stepper dwell to lock axes for a defined amount of time to ensure the axes come to a complete
// stop and not drift from residual inertial forces at the end of the last movement.
delay_ms(settings.stepper_idle_lock_time);
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) {
STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
} else {
STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT);
}
}
}
// "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based
// on the Pramod Ranade inverse time stepper algorithm, where a timer ticks at a constant
// frequency and uses time-distance counters to track when its the approximate time for any
// step event. However, the Ranade algorithm, as described, is susceptible to numerical round-off,
// meaning that some axes steps may not execute for a given multi-axis motion.
// Grbl's algorithm slightly differs by using a single Ranade time-distance counter to manage
// a Bresenham line algorithm for multi-axis step events which ensures the number of steps for
// each axis are executed exactly. In other words, it uses a Bresenham within a Bresenham algorithm,
// where one tracks time(Ranade) and the other steps.
// This interrupt pops blocks from the block_buffer and executes them by pulsing the stepper pins
// appropriately. It is supported by The Stepper Port Reset Interrupt which it uses to reset the
// stepper port after each pulse. The bresenham line tracer algorithm controls all three stepper
// outputs simultaneously with these two interrupts.
//
// NOTE: Average time in this ISR is: 5 usec iterating timers only, 20-25 usec with step event, or
// 15 usec when popping a block. So, ensure Ranade frequency and step pulse times work with this.
ISR(TIMER2_COMPA_vect)
{
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
if (busy) { return; } // The busy-flag is used to avoid reentering this interrupt
// Pulse stepper port pins, if flagged. New block dir will always be set one timer tick
// before any step pulse due to algorithm design.
if (st.execute_step) {
st.execute_step = false;
STEPPING_PORT = ( STEPPING_PORT & ~(DIRECTION_MASK | STEP_MASK) ) | out_bits;
TCNT0 = step_pulse_time; // Reload Timer0 counter.
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
}
busy = true;
sei(); // Re-enable interrupts. This ISR will still finish before returning to main program.
// If there is no current block, attempt to pop one from the buffer
if (current_block == NULL) {
// Anything in the buffer? If so, initialize next motion.
current_block = plan_get_current_block();
if (current_block != NULL) {
// By algorithm design, the loading of the next block never coincides with a step event,
// since there is always one Ranade timer tick before a step event occurs. This means
// that the Bresenham counter math never is performed at the same time as the loading
// of a block, hence helping minimize total time spent in this interrupt.
// Initialize direction bits for block
out_bits = current_block->direction_bits ^ settings.invert_mask;
st.execute_step = true; // Set flag to set direction bits.
// Initialize Bresenham variables
st.counter_x = (current_block->step_event_count >> 1);
st.counter_y = st.counter_x;
st.counter_z = st.counter_x;
st.event_count = current_block->step_event_count;
st.step_events_remaining = st.event_count;
// During feed hold, do not update Ranade counter, rate, or ramp type. Keep decelerating.
if (sys.state == STATE_CYCLE) {
// Initialize Ranade variables
st.d_counter = current_block->d_next;
st.delta_d = current_block->initial_rate;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
// Initialize ramp type.
if (st.step_events_remaining == current_block->decelerate_after) { st.ramp_type = DECEL_RAMP; }
else if (st.delta_d == current_block->nominal_rate) { st.ramp_type = CRUISE_RAMP; }
else { st.ramp_type = ACCEL_RAMP; }
}
} else {
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end
return; // Nothing to do but exit.
}
}
// Adjust inverse time counter for ac/de-celerations
if (st.ramp_type) {
// Tick acceleration ramp counter
st.ramp_count--;
if (st.ramp_count == 0) {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
if (st.ramp_type == ACCEL_RAMP) { // Adjust velocity for acceleration
st.delta_d += current_block->rate_delta;
if (st.delta_d >= current_block->nominal_rate) { // Reached cruise state.
st.ramp_type = CRUISE_RAMP;
st.delta_d = current_block->nominal_rate; // Set cruise velocity
}
} else if (st.ramp_type == DECEL_RAMP) { // Adjust velocity for deceleration
if (st.delta_d > current_block->rate_delta) {
st.delta_d -= current_block->rate_delta;
} else {
st.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow.
}
}
}
}
// Iterate Pramod Ranade inverse time counter. Triggers each Bresenham step event.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_counter -= MINIMUM_STEP_RATE; }
else { st.d_counter -= st.delta_d; }
// Execute Bresenham step event, when it's time to do so.
if (st.d_counter < 0) {
st.d_counter += current_block->d_next;
// Check for feed hold state and execute accordingly.
if (sys.state == STATE_HOLD) {
if (st.ramp_type != DECEL_RAMP) {
st.ramp_type = DECEL_RAMP;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
}
if (st.delta_d <= current_block->rate_delta) {
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP);
return;
}
}
// TODO: Vary Bresenham resolution for smoother motions or enable faster step rates (>20kHz).
out_bits = current_block->direction_bits; // Reset out_bits and reload direction bits
st.execute_step = true;
// Execute step displacement profile by Bresenham line algorithm
st.counter_x -= current_block->steps[X_AXIS];
if (st.counter_x < 0) {
out_bits |= (1<<X_STEP_BIT);
st.counter_x += st.event_count;
if (out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; }
}
st.counter_y -= current_block->steps[Y_AXIS];
if (st.counter_y < 0) {
out_bits |= (1<<Y_STEP_BIT);
st.counter_y += st.event_count;
if (out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; }
}
st.counter_z -= current_block->steps[Z_AXIS];
if (st.counter_z < 0) {
out_bits |= (1<<Z_STEP_BIT);
st.counter_z += st.event_count;
if (out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; }
}
// Check step events for trapezoid change or end of block.
st.step_events_remaining--; // Decrement step events count
if (st.step_events_remaining) {
if (st.ramp_type != DECEL_RAMP) {
// Acceleration and cruise handled by ramping. Just check for deceleration.
if (st.step_events_remaining <= current_block->decelerate_after) {
st.ramp_type = DECEL_RAMP;
if (st.step_events_remaining == current_block->decelerate_after) {
if (st.delta_d == current_block->nominal_rate) {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Set ramp counter for trapezoid
} else {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK-st.ramp_count; // Set ramp counter for triangle
}
}
}
}
} else {
// If current block is finished, reset pointer
current_block = NULL;
plan_discard_current_block();
}
out_bits ^= settings.invert_mask; // Apply step port invert mask
}
busy = false;
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT;
}
// The Stepper Port Reset Interrupt: Timer0 OVF interrupt handles the falling edge of the
// step pulse. This should always trigger before the next Timer2 COMPA interrupt and independently
// finish, if Timer2 is disabled after completing a move.
ISR(TIMER0_OVF_vect)
{
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.invert_mask & STEP_MASK);
TCCR0B = 0; // Disable timer until needed.
}
// Reset and clear stepper subsystem variables
void st_reset()
{
memset(&st, 0, sizeof(st));
current_block = NULL;
busy = false;
}
// Initialize and start the stepper motor subsystem
void st_init()
{
// Configure directions of interface pins
STEPPING_DDR |= STEPPING_MASK;
STEPPING_PORT = (STEPPING_PORT & ~STEPPING_MASK) | settings.invert_mask;
STEPPERS_DISABLE_DDR |= 1<<STEPPERS_DISABLE_BIT;
// Configure Timer 2
TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt while configuring it
TCCR2B = 0; // Disable Timer2 until needed
TCNT2 = 0; // Clear Timer2 counter
TCCR2A = (1<<WGM21); // Set CTC mode
OCR2A = (F_CPU/ISR_TICKS_PER_SECOND)/8 - 1; // Set Timer2 CTC rate
// Configure Timer 0
TIMSK0 &= ~(1<<TOIE0);
TCCR0A = 0; // Normal operation
TCCR0B = 0; // Disable Timer0 until needed
TIMSK0 |= (1<<TOIE0); // Enable overflow interrupt
// Start in the idle state, but first wake up to check for keep steppers enabled option.
st_wake_up();
st_go_idle();
}
// Planner external interface to start stepper interrupt and execute the blocks in queue. Called
// by the main program functions: planner auto-start and run-time command execution.
void st_cycle_start()
{
if (sys.state == STATE_QUEUED) {
sys.state = STATE_CYCLE;
st_wake_up();
}
}
// Execute a feed hold with deceleration, only during cycle. Called by main program.
void st_feed_hold()
{
if (sys.state == STATE_CYCLE) {
sys.state = STATE_HOLD;
sys.auto_start = false; // Disable planner auto start upon feed hold.
}
}
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
// runtime command execution in the main program, ensuring that the planner re-plans safely.
// NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
// cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
// Only the planner de/ac-celerations profiles and stepper rates have been updated.
void st_cycle_reinitialize()
{
if (current_block != NULL) {
// Replan buffer from the feed hold stop location.
plan_cycle_reinitialize(st.step_events_remaining);
st.ramp_type = ACCEL_RAMP;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
st.delta_d = 0;
sys.state = STATE_QUEUED;
} else {
sys.state = STATE_IDLE;
}
}