/*
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 RAMP_NOOP_CRUISE 0
#define RAMP_ACCEL 1
#define RAMP_DECEL 2
#define LOAD_NOOP 0
#define LOAD_LINE 1
#define LOAD_BLOCK 2
#define ST_NOOP 0
#define ST_END_OF_BLOCK 1
// 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;
int8_t segment_steps_remaining; // Steps remaining in line motion
// Used by inverse time algorithm to track step rate
int32_t counter_d; // Inverse time distance traveled since last step event
int32_t delta_d; // Inverse time distance traveled per interrupt tick
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;
uint8_t ramp_count;
uint8_t ramp_type;
} stepper_t;
static stepper_t st;
#define SEGMENT_BUFFER_SIZE 5
// Stores stepper buffer common data. Can change planner mid-block in special conditions.
typedef struct {
int32_t step_events_remaining; // Tracks step event count for the executing planner block
int32_t d_next; // Scaled distance to next step
float mm_per_step;
} st_data_t;
static st_data_t segment_data[SEGMENT_BUFFER_SIZE];
// Primary stepper motion buffer
typedef struct {
uint8_t n_step;
int32_t rate;
uint8_t st_data_index;
uint8_t flag;
} st_segment_t;
static st_segment_t segment_buffer[SEGMENT_BUFFER_SIZE];
static volatile uint8_t segment_buffer_tail;
static uint8_t segment_buffer_head;
static uint8_t segment_next_head;
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;
static plan_block_t *pl_prep_block; // A pointer to the planner block being prepped into the stepper buffer
static uint8_t pl_prep_index;
static st_data_t *st_prep_data;
static uint8_t st_data_prep_index;
// Returns the index of the next block in the ring buffer
// NOTE: Removed modulo (%) operator, which uses an expensive divide and multiplication.
static uint8_t next_block_index(uint8_t block_index)
{
block_index++;
if (block_index == SEGMENT_BUFFER_SIZE) { block_index = 0; }
return(block_index);
}
/* __________________________
/| |\ _________________ ^
/ | | \ /| |\ |
/ | | \ / | | \ 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<n_step;
st.delta_d = st_current_segment->rate;
// 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[st_current_segment->st_data_index];
// Initialize direction bits for block
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.
// Initialize Bresenham line counters
st.counter_x = (pl_current_block->step_event_count >> 1);
st.counter_y = st.counter_x;
st.counter_z = st.counter_x;
// Initialize inverse time and step rate counter data
st.counter_d = st_current_data->d_next; // d_next always greater than delta_d.
}
st.load_flag = LOAD_NOOP; // 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 += st_current_data->d_next; // Reload inverse time counter
st.out_bits = pl_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 -= pl_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<flag == ST_END_OF_BLOCK) {
plan_discard_current_block();
st.load_flag = LOAD_BLOCK;
} else {
st.load_flag = LOAD_LINE;
}
// Discard current block
if (segment_buffer_head != segment_buffer_tail) {
segment_buffer_tail = next_block_index( segment_buffer_tail );
}
// 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.
}
st.out_bits ^= settings.invert_mask; // Apply step port invert mask
}
busy = false;
// SPINDLE_ENABLE_PORT ^= 1<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;
}
}
/* Preps stepper buffer. Called from main program.
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.
TODO: Need to describe the importance of continuations of step pulses between ramp states
and planner blocks. This has to do with Alden's problem with step "phase". The things I've
been doing here limit this phase issue by truncating some of the ramp timing for certain
events like deceleration initialization and end of block.
*/
void st_prep_buffer()
{
while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer.
// 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);
if (pl_prep_block == NULL) { return; } // No more planner blocks. Let stepper finish out.
// Prepare commonly shared planner block data for the ensuing step buffer moves
st_data_prep_index = next_block_index(st_data_prep_index);
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 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: If the planner updates this block, particularly from a deceleration to an acceleration,
// we must reload the initial rate data, such that the velocity profile is re-constructed correctly.
// st_prep_data->initial_rate = ceil(sqrt(pl_prep_block->entry_speed_sqr)*(INV_TIME_MULTIPLIER/(60*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)
// 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.
// 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)
// 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.
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->mm_per_step = pl_prep_block->millimeters/pl_prep_block->step_event_count;
}
/*
// Check if planner has changed block exit parameters. If so, then we have to update the
// velocity profile for the remainder of this block. Otherwise, the block hasn't changed.
if (exit_speed_sqr != last_exit_speed_sqr) {
intersect_distance = 0.5*( millimeters + (entry_speed_sqr-exit_speed_sqr)/(2*acceleration) );
if (intersect_distance <= 0) { // Deceleration only.
final_rate_sqr = initial_rate_sqr - 2*acceleration*segment_distance;
block->decelerate_after = 0;
} else {
decelerate_after = (block->nominal_speed_sqr - exit_speed_sqr)/(2*block->acceleration);
if (decelerate_after > intersect_distance) { decelerate_after = intersect_distance; }
if (decelerate_after > block->millimeters) { decelerate_after = block->millimeters; }
}
}
n_step = 100; // Estimate distance we're going to travel in this segment
if (n_step > step_events_remaining) { n_step = step_events_remaining; };
segment_distance = n_step*mm_per_step; // True distance traveled
// ISSUE: Either weighted average speed or truncated segments must be used. Otherwise
// accelerations, in particular high value, will not perform correctly.
if (distance_traveled < decelerate_after) {
if (segment_distance + distance_traveled > decelerate_after) {
n_step = ceil((decelerate_after-distance_traveled)/mm_per_step);
segment_distance = n_step*mm_per_step;
}
}
// based on time?
time = incremental time;
v_exit = v_entry + acceleration*time;
if (v_exit > v_nominal) {
time_accel = (v_nominal-v_entry)/acceleration;
distance = v_entry*time_accel + 0.5*acceleration*time_accel**2;
time_remaining = time - time_accel;
}
distance = v_entry*time + 0.5*acceleration*time*time;
t_accel = (v_nominal - v_entry) / acceleration;
t_decel = (v_exit - v_nominal) / -acceleration;
dt = (v_entry-v_exit)/acceleration;
if
// What's the speed of this segment? Computing per segment can get expensive, but this
// would allow S-curves to be installed pretty easily here.
if (initial_speed < nominal_speed) {
if (initial_distance < decelerate_after) { // Acceleration
exit_speed = sqrt(initial_speed*initial_speed + 2*acceleration*distance);
if (exit_speed > nominal_speed) { exit_speed = nominal_speed; }
} else { // Deceleration
exit_speed = sqrt(initial_speed*initial_speed - 2*acceleration*distance);
}
average_speed = 0.5*(initial_speed + exit_speed);
} else { // Cruise
average_speed = nominal_speed;
}
segment_rate = ceil(average_speed*(INV_TIME_MULTIPLIER/(60*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
if (segment_rate < MINIMUM_STEP_RATE) { segment_rate = MINIMUM_STEP_RATE; }
*/
/*
TODO: Need to check for a planner flag to indicate a change to this planner block.
If so, need to check for a change in acceleration state, from deceleration to acceleration,
to reset the stepper ramp counters and the initial_rate data to trace the new
ac/de-celeration profile correctly.
No change conditions:
- From nominal speed to acceleration from feedrate override
- From nominal speed to new deceleration.
- From acceleration to new deceleration point later or cruising point.
- From acceleration to immediate deceleration? Can happen during feedrate override
and slowing down, but likely ok by enforcing the normal ramp counter protocol.
Change conditions:
- From deceleration to acceleration, i.e. common with jogging when new blocks are added.
*/
st_segment_t *st_prep_block = &segment_buffer[segment_buffer_head];
st_prep_block->st_data_index = st_data_prep_index;
// TODO: How do you cheaply compute n_step without a sqrt()? Could be performed as 'bins'.
st_prep_block->n_step = 100; //floor( (exit_speed*approx_time)/mm_per_step );
// st_segment->n_step = max(st_segment->n_step,MINIMUM_STEPS_PER_BLOCK); // Ensure it moves for very slow motions?
// st_segment->n_step = min(st_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 (st_prep_block->n_step > st_prep_data->step_events_remaining) {
st_prep_block->n_step = st_prep_data->step_events_remaining;
}
// Check if n_step exceeds decelerate point in block. Need to perform this so that the
// ramp counters are reset correctly in the stepper algorithm. Can be 1 step, but should
// be OK since it is likely moving at a fast rate already.
if (st_prep_block->n_step > pl_prep_block->decelerate_after) {
st_prep_block->n_step = pl_prep_block->decelerate_after;
}
float distance, exit_speed_sqr;
distance = st_prep_block->n_step*st_prep_data->mm_per_step; // Always greater than zero
if (st_prep_data->step_events_remaining >= pl_prep_block->decelerate_after) {
exit_speed_sqr = pl_prep_block->entry_speed_sqr - 2*pl_prep_block->acceleration*distance;
// Set ISR tick reset flag for deceleration ramp.
} else { // Acceleration or cruising ramp
if (pl_prep_block->entry_speed_sqr < pl_prep_block->nominal_speed_sqr) {
exit_speed_sqr = pl_prep_block->entry_speed_sqr + 2*pl_prep_block->acceleration*distance;
if (exit_speed_sqr > pl_prep_block->nominal_speed_sqr) { exit_speed_sqr = pl_prep_block->nominal_speed_sqr; }
} else {
exit_speed_sqr = pl_prep_block->nominal_speed_sqr;
}
}
// Adjust inverse time counter for ac/de-celerations
if (st.ramp_type) {
st.ramp_count--; // Tick acceleration ramp counter
if (st.ramp_count == 0) { // Adjust step rate when its time
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
if (st.ramp_type == RAMP_ACCEL) { // Adjust velocity for acceleration
st.delta_d += st_current_data->rate_delta;
if (st.delta_d >= st_current_data->nominal_rate) { // Reached nominal rate.
st.delta_d = st_current_data->nominal_rate; // Set cruising velocity
st.ramp_type = RAMP_NOOP_CRUISE; // Set ramp flag to ignore
}
} else { // Adjust velocity for deceleration
if (st.delta_d > st_current_data->rate_delta) {
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.
// Check for and handle feed hold exit? At this point, machine is stopped.
}
}
// 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; }
}
}
// During feed hold, do not update rate or ramp type. Keep decelerating.
if (sys.state == STATE_CYCLE) {
st.delta_d = st_current_data->initial_rate;
if (st.delta_d == st_current_data->nominal_rate) {
ramp_type = RAMP_NOOP_CRUISE;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Set ramp counter for trapezoid
}
// Acceleration and cruise handled by ramping. Just check for deceleration.
if (st_current_segment->flag == ST_NOOP) {
if (st.ramp_type == RAMP_NOOP_CRUISE) {
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
}
st.ramp_type = RAMP_DECEL;
}
// Update planner block variables.
pl_prep_block->entry_speed_sqr = max(0.0,exit_speed_sqr);
// pl_prep_block->max_entry_speed_sqr = exit_speed_sqr; // ??? Overwrites the corner speed. May need separate variable.
pl_prep_block->millimeters -= distance; // Potential round-off error near end of block.
pl_prep_block->millimeters = max(0.0,pl_prep_block->millimeters); // Shouldn't matter.
// Update stepper block variables.
st_prep_data->step_events_remaining -= st_prep_block->n_step;
if ( st_prep_data->step_events_remaining == 0 ) {
// Move planner pointer to next block
st_prep_block->flag = ST_END_OF_BLOCK;
pl_prep_index = next_block_index(pl_prep_index);
pl_prep_block = NULL;
} else {
st_prep_block->flag = ST_NOOP;
}
// New step block completed. Increment step buffer indices.
segment_buffer_head = segment_next_head;
segment_next_head = next_block_index(segment_buffer_head);
}
}