/*
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 .
*/
#include
#include "stepper.h"
#include "config.h"
#include "settings.h"
#include "planner.h"
#include "nuts_bolts.h"
// Some useful constants
#define TICKS_PER_MICROSECOND (F_CPU/1000000)
#define CRUISE_RAMP 0
#define ACCEL_RAMP 1
#define DECEL_RAMP 2
#define LOAD_NOOP 0
#define LOAD_LINE 1
#define LOAD_BLOCK 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 step_events_remaining; // Steps remaining in line motion
// Used by inverse time algorithm
int32_t delta_d; // Inverse time distance traveled per interrupt tick
int32_t d_counter; // Inverse time distance traveled since last step event
int32_t d_per_tick;
// Used by the stepper driver 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;
} stepper_t;
static stepper_t st;
#define STEPPER_BUFFER_SIZE 5
typedef struct {
int32_t event_count;
int32_t rate;
uint8_t end_of_block;
uint8_t tick_count;
int32_t initial_rate; // The step rate at start of block
int32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
int32_t decelerate_after; // The index of the step event on which to start decelerating
int32_t nominal_rate; // The nominal step rate for this block in step_events/minute
int32_t d_next; // Scaled distance to next step
} stepper_buffer_t;
static stepper_buffer_t step_buffer[STEPPER_BUFFER_SIZE];
static volatile uint8_t step_buffer_tail;
static uint8_t step_buffer_head;
static uint8_t step_next_head;
// 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.
static plan_block_t *plan_current_block; // A pointer to the planner block currently being traced
/* __________________________
/| |\ _________________ ^
/ | | \ /| |\ |
/ | | \ / | | \ 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<> 3);
// Enable stepper driver interrupt
st.execute_step = false;
TCNT2 = 0; // Clear Timer2
TIMSK2 |= (1<direction_bits ^ settings.invert_mask;
st.execute_step = true; // Set flag to set direction bits.
st.counter_x = (plan_current_block->step_event_count >> 1);
st.counter_y = st.counter_x;
st.counter_z = st.counter_x;
// This is correct. Sets the total time before the next step occurs.
st.counter_d = plan_current_block->d_next; // d_next always greater than delta_d.
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Set ramp counter for trapezoid
}
st.load_flag = LOAD_NOOP; // Line motion loaded. Set no-operation flag until complete.
} else {
// Can't discard planner block here if a feed hold stops in middle of block.
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end
return; // Nothing to do but exit.
}
}
// Iterate inverse time counter. Triggers each Bresenham step event.
st.counter_d -= st.delta_d;
// Execute Bresenham step event, when it's time to do so.
if (st.counter_d < 0) {
st.counter_d += plan_current_block->d_next; // Reload inverse time counter
st.out_bits = plan_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 -= plan_current_block->steps[X_AXIS];
if (st.counter_x < 0) {
st.out_bits |= (1<step_event_count;
if (st.out_bits & (1<steps[Y_AXIS];
if (st.counter_y < 0) {
st.out_bits |= (1<step_event_count;
if (st.out_bits & (1<steps[Z_AXIS];
if (st.counter_z < 0) {
st.out_bits |= (1<step_event_count;
if (st.out_bits & (1<step_event_count;
//st.step_events_remaining = st.event_count;
// Convert new block to stepper variables.
// NOTE: This data can change mid-block from normal planner updates and feedrate overrides. Must
// be maintained as these execute.
// TODO: The initial rate needs to be sent back to the planner to update the entry speed
block->initial_rate = ceil(sqrt(plan_current_block->entry_speed_sqr)*(INV_TIME_MULTIPLIER/(60*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
block->nominal_rate = ceil(plan_current_block->nominal_speed*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
// This data doesn't change. Could be performed in the planner, but fits nicely here.
// Although, acceleration can change for S-curves. So keep it here.
block->rate_delta = ceil(plan_current_block->acceleration*
((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic)
// This definitely doesn't change, but could be precalculated in a way to help some of the
// math in this handler, i.e. millimeters per step event data.
block->d_next = ceil((plan_current_block->millimeters*INV_TIME_MULTIPLIER)/plan_current_block->step_event_count); // (mult*mm/step)
// During feed hold, do not update Ranade counter, rate, or ramp type. Keep decelerating.
if (sys.state == STATE_CYCLE) { }
}
// Track instead the trapezoid line and use the average of the entry and exit velocities
// to determine step rate. This should take care of the deceleration issue automatically...
// i think.
// First need to figure out what type of profile is segment is, i.e. acceleration only, accel
// to decel triangle, cruise to decel, or all three. May need more profile data to compute this
// from the planner itself, like accelerate until.
// Another issue. This is only tracking the velocity profile, not the distance covered over that
// time period. This can lead to an unsynchronized velocity profile and steps executed. But,
// how much drift is there really? Enough to be a problem? Not sure. I would think typical
// drift would be on the order of a few steps, more depending on step resolution.
entry_rate = last_exit_rate;
time = 250 ISR ticks per acceleration tick.
distance = 0;
if (distance_traveled < accelerate_until)
exit_rate = entry_rate + acceleration*time;
if (exit_rate > nominal_rate) {
exit_rate = nominal_rate;
time = 2*(accelerate_until-distance_travel)/(entry_rate+nominal_rate);
// distance = accelerate_until; // Enforce distance?
// Truncate this segment.
}
} else if (distance_traveled >= decelerate_after) {
if (accelerate_until == decelerate_after) {
time = last time;
exit_rate = entry_rate;
} else {
exit_rate = entry_rate - acceleration*time;
}
} else {
exit_rate = nominal_rate; // Just cruise
distance = nominal_rate*time;
if (distance > decelerate_after) { // Truncate segment at nominal rate.
time = (decelerate_after-distance_traveled)/(nominal_rate);
distance = decelerate_after;
}
}
mean_rate = 0.5*(entry_rate+exit_rate);
distance = mean_rate*time;
if (entry_rate < nominal_rate) {
if (entry_distance < decelerate_after) { // Acceleration case
exit_rate = entry_rate + acceleration*time
exit_rate = min(exit_rate,nominal_rate);
mean_rate = 0.5*(entry_rate + exit_rate);
distance = mean_rate*time;
if (distance > decelerate_after) {
exit_rate =
// If the MINIMUM_STEP_RATE is less than ACCELERATION_TICKS_PER_SECOND then there can be
// rate adjustements that have less than one step per tick.
// How do you deal with the remainer?
time = 250 ISR ticks per acceleration tick. (30000/120)
delta_d*time // mm per acceleration tick
delta_d*time/d_next // number of steps/acceleration_tick. Chance of integer overflow.
delta_d*time/d_next + last_remainder. // steps/acceleration_tick.
n_step*d_next/delta_d // number of ISR ticks for enforced n_steps.
// In floating point? Then convert?
// Requires exact millimeters. Roundoff might be a problem. But could be corrected by just
// checking if the total step event counts are performed.
// Could be limited by float conversion and about 1e7 steps per block.
line_mm = feed_rate / acc_tick // mm per acc_tick
n_steps = floor(line_mm * step_event_remaining/millimeters_remaining) // steps. float 7.2 digits|int32 10 digits
millimeters_remaining -= line_mm;
step_events_remaining -= n_steps;
// There doesn't seem to be a way to avoid this divide here.
line_mm = feed_rate / acc_tick // mm per acc_tick
n_steps = floor( (line_mm+line_remainder) * step_event_count/millimeters) // steps. float 7.2 digits|int32 10 digits
line_remainder = line_mm - n_steps*(millimeters/step_event_count);
// Need to handle when rate is very very low, i.e. less than one step per accel tick.
// Could be bounded by MINIMUM_STEP_RATE.
// 1. Figure out how many steps occur exactly within n ISR ticks.
// 2. Account for step-time remainder for next line motion exactly.
// 3. At the end of block, determine exact number of ISR ticks to finish the steps. Or,\
have the ISR track steps to exit on time. It would require an extra counter.
// NOTE: There doesn't seem to be a great way to figure out how many steps occur within
// a set number of ISR ticks. Numerical round-off and CPU overhead always seems to be a
// critical problem. So, either numerical round-off checks could be made to account for
// them, while CPU overhead could be minimized in some way, or we can flip the algorithm
// around to have the stepper algorithm track number of steps over an indeterminant amount
// of time instead.
// In other words, we use the planner velocity floating point data to get an estimate of
// the number of steps we want to execute. We then back out the approximate velocity for
// the planner to use, which should be much more robust to round-off error. The main problem
// now is that we are loading the stepper algorithm to handle acceleration now, rather than
// pre-calculating with the main program. This approach does make sense in the way that
// planner velocities and stepper profiles can be traced more accurately.
// Which is better? Very hard to tell. The time-based algorithm would be able to handle
// Bresenham step adaptive-resolution much easier and cleaner. Whereas, the step-based would
// require some additional math in the stepper algorithm to adjust on the fly, plus adaptation
// would occur in a non-deterministic manner.
// I suppose it wouldn't hurt to build both to see what's better. Just a lot more work.
feed_rate/120 = millimeters per acceleration tick
steps?
d_next // (mult*mm/step)
rate // (mult*mm/isr_tic)
rate/d_next // step/isr_tic
if (plan_current_block->step_events_remaining <= plan_current_block->decelerate_after) {
// Determine line segment velocity and associated inverse time counter.
if (step_block.ramp_type == ACCEL_RAMP) { // Adjust velocity for acceleration
step_block.delta_d += plan_current_block->rate_delta;
if (step_block.delta_d >= plan_current_block->nominal_rate) { // Reached cruise state.
step_block.ramp_type = CRUISE_RAMP;
step_block.delta_d = plan_current_block->nominal_rate; // Set cruise velocity
}
}
} else { // Adjust velocity for deceleration
if (step_block.delta_d > plan_current_block->rate_delta) {
step_block.delta_d -= plan_current_block->rate_delta;
} else {
step_block.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow.
}
}
// Incorrect. Can't overwrite delta_d. Needs to override instead.
if (step_block.delta_d < MINIMUM_STEP_RATE) { step_block.delta_d = MINIMUM_STEP_RATE; }
/* - Compute the number of steps needed to complete this move over the move time, i.e.
ISR_TICKS_PER_ACCELERATION_TICK.
- The first block in the buffer is half of the move time due to midpoint rule.
- Check if this reaches the deceleration after location. If so, truncate move. Also,
if this is a triangle move, double the truncated move to stay with midpoint rule.
NOTE: This can create a stepper buffer move down to just one step in length.
- Update the planner block entry speed for the planner to compute from end of the
stepper buffer location.
- If a feed hold occurs, begin to enforce deceleration, while enforcing the above rules.
When the deceleration is complete, all we need to do is update the planner block
entry speed and force a replan.
*/
// Planner block move completed.
// TODO: planner buffer tail no longer needs to be volatile. only accessed by main program.
if (st.step_events_remaining == 0) {
plan_current_block = NULL; // Set flag that we are done with this planner block.
plan_discard_current_block();
}
step_buffer_head = step_next_head;
step_next_head = next_block_index(step_buffer_head);
}
}