/* 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_SEGMENT 1 #define LOAD_BLOCK 2 #define SEGMENT_NOOP 0 #define SEGMENT_END_OF_BLOCK bit(0) #define RAMP_CHANGE_ACCEL bit(1) #define RAMP_CHANGE_DECEL bit(2) #define MINIMUM_STEPS_PER_SEGMENT 1 // Don't change #define SEGMENT_BUFFER_SIZE 6 #define DT_SEGMENT (1/ACCELERATION_TICKS_PER_SECOND) // 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; // Used by inverse time algorithm to track step rate int32_t counter_dist; // Inverse time distance traveled since last step event uint8_t step_count; // Steps remaining in line segment motion uint8_t phase_count; // Phase ticks remaining after line segment steps complete // 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; // Stores stepper common data for executing steps in the segment buffer. Data can change mid-block when the // planner updates the remaining block velocity profile with a more optimal plan or a feedrate override occurs. // 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 { // TODO: Retain step[N_AXIS], step_event_count, and direction byte here, so that we can throw // away the planner block when the segment prep is complete. float step_events_remaining; // Tracks step event count for the executing planner block // Planner block velocity profile parameters used to trace and execute steps.; float accelerate_until; float decelerate_after; float current_speed; float maximum_speed; float exit_speed; } st_data_t; static st_data_t segment_data[SEGMENT_BUFFER_SIZE-1]; // Primary stepper segment ring buffer. Contains small, short line segments for the stepper algorithm to execute, // which are "checked-out" incrementally from the first block in the planner buffer. Once "checked-out", the steps // 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 n_phase_tick; uint32_t dist_per_tick; 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; 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; // 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 static float st_prep_step_per_mm; // TODO: All this stuff only needs to be retained for the prepped planner block. Once changed // or complete, we do not need this information anymore. Duh! // typedef struct { // float step_events_remaining; // Tracks step event count for the executing planner block // float accelerate_until; // float decelerate_after; // float current_speed; // float maximum_speed; // float exit_speed; // float step_per_mm; // } st_prep_data_t; /* __________________________ /| |\ _________________ ^ / | | \ /| |\ | / | | \ / | | \ 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; st.load_flag = LOAD_BLOCK; TCNT2 = 0; // Clear Timer2 TIMSK2 |= (1<n_step; // 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) { 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]; // 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.execute_step = true; // 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, step rate data, and acceleration ramp counters st.counter_dist = INV_TIME_MULTIPLIER; // dist_per_step always greater than dist_per_tick. } st.load_flag = LOAD_NOOP; // Segment motion loaded. Set no-operation flag to skip during execution. } 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_dist -= st_current_segment->dist_per_tick; // Execute Bresenham step event, when it's time to do so. if (st.counter_dist < 0) { if (st.step_count > 0) { // Block phase correction from executing step. st.counter_dist += INV_TIME_MULTIPLIER; // 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 & SEGMENT_END_OF_BLOCK) { plan_discard_current_block(); st.load_flag = LOAD_BLOCK; } else { st.load_flag = LOAD_SEGMENT; } // Discard current segment by advancing buffer tail index if ( ++segment_buffer_tail == SEGMENT_BUFFER_SIZE) { segment_buffer_tail = 0; } } st.phase_count--; } busy = false; // SPINDLE_ENABLE_PORT ^= 1<step_events_remaining); // st.ramp_type = RAMP_ACCEL; // st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK/2; // st.ramp_rate = 0; // sys.state = STATE_QUEUED; // } else { // 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 fixed 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 must also be kept consistent. Meaning that, if the last segment step pulses right before a segment 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 can get 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 by retaining the count remainders, we don't have to explicitly and expensively track and synchronize the exact number of steps, time, and 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 comes back. 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() { if (sys.state == STATE_QUEUED) { return; } // Block until a motion state is issued 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 so, prepare step data. 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; 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]; st_prep_step_per_mm = pl_prep_block->step_event_count/pl_prep_block->millimeters; // Initialize planner block step data st_prep_data->step_events_remaining = pl_prep_block->step_event_count; } st_prep_data->current_speed = sqrt(pl_prep_block->entry_speed_sqr); // Determine current block exit speed plan_block_t *pl_next_block = plan_get_block_by_index(plan_next_block_index(pl_prep_index)); float exit_speed_sqr; if (pl_next_block == NULL) { exit_speed_sqr = 0.0; st_prep_data->exit_speed = 0.0; } else { exit_speed_sqr = pl_next_block->entry_speed_sqr; st_prep_data->exit_speed = sqrt(exit_speed_sqr); } // st_prep_data->accelerate_until = 0.5*(pl_prep_block->nominal_speed_sqr-pl_prep_block->entry_speed_sqr)/(pl_prep_block->acceleration); // st_prep_data->decelerate_after = 0.5*(pl_prep_block->nominal_speed_sqr-exit_speed_sqr)/(pl_prep_block->acceleration); // if (pl_prep_block->millimeters < st_prep_data->accelerate_until+st_prep_data->decelerate_after) { // st_prep_data->decelerate_after = 0.5*( pl_prep_block->millimeters + 0.5*(pl_prep_block->entry_speed_sqr // - exit_speed_sqr)/(pl_prep_block->acceleration) ); // st_prep_data->accelerate_until = pl_prep_block->millimeters-st_prep_data->decelerate_after; // st_prep_data->maximum_speed = sqrt(2*pl_prep_block->acceleration*st_prep_data->decelerate_after+exit_speed_sqr); // } else { // st_prep_data->accelerate_until = pl_prep_block->millimeters-st_prep_data->accelerate_until; // st_prep_data->maximum_speed = sqrt(pl_prep_block->nominal_speed_sqr); // } // Determine velocity profile based on the 7 possible types: Cruise-only, cruise-deceleration, // acceleration-cruise, acceleration-only, deceleration-only, trapezoid, and triangle. st_prep_data->accelerate_until = pl_prep_block->millimeters; if (pl_prep_block->entry_speed_sqr == pl_prep_block->nominal_speed_sqr) { st_prep_data->maximum_speed = sqrt(pl_prep_block->nominal_speed_sqr); if (exit_speed_sqr == pl_prep_block->nominal_speed_sqr) { // Cruise-only type st_prep_data->decelerate_after = 0.0; } else { // Cruise-deceleration type st_prep_data->decelerate_after = 0.5*(pl_prep_block->nominal_speed_sqr-exit_speed_sqr)/(pl_prep_block->acceleration); } } else if (exit_speed_sqr == pl_prep_block->nominal_speed_sqr) { // Acceleration-cruise type st_prep_data->maximum_speed = sqrt(pl_prep_block->nominal_speed_sqr); st_prep_data->decelerate_after = 0.0; st_prep_data->accelerate_until -= 0.5*(pl_prep_block->nominal_speed_sqr-pl_prep_block->entry_speed_sqr)/(pl_prep_block->acceleration); } else { float intersection_dist = 0.5*( pl_prep_block->millimeters + 0.5*(pl_prep_block->entry_speed_sqr - exit_speed_sqr)/(pl_prep_block->acceleration) ); if (intersection_dist > 0.0) { if (intersection_dist < pl_prep_block->millimeters) { // Either trapezoid or triangle types st_prep_data->decelerate_after = 0.5*(pl_prep_block->nominal_speed_sqr-exit_speed_sqr)/(pl_prep_block->acceleration); if (st_prep_data->decelerate_after < intersection_dist) { // Trapezoid type st_prep_data->maximum_speed = sqrt(pl_prep_block->nominal_speed_sqr); st_prep_data->accelerate_until -= 0.5*(pl_prep_block->nominal_speed_sqr-pl_prep_block->entry_speed_sqr)/(pl_prep_block->acceleration); } else { // Triangle type st_prep_data->decelerate_after = intersection_dist; st_prep_data->maximum_speed = sqrt(2*pl_prep_block->acceleration*st_prep_data->decelerate_after+exit_speed_sqr); st_prep_data->accelerate_until -= st_prep_data->decelerate_after; } } else { // Deceleration-only type st_prep_data->maximum_speed = st_prep_data->current_speed; st_prep_data->decelerate_after = pl_prep_block->millimeters; } } else { // Acceleration-only type st_prep_data->maximum_speed = st_prep_data->exit_speed; st_prep_data->decelerate_after = 0.0; st_prep_data->accelerate_until = 0.0; } } } // Set new segment to point to the current segment data block. prep_segment->st_data_index = st_data_prep_index; // ----------------------------------------------------------------------------------- // Initialize segment execute distance. Attempt to create a full segment over DT_SEGMENT. float mm_remaining = pl_prep_block->millimeters; float dt = DT_SEGMENT; if (mm_remaining > st_prep_data->accelerate_until) { // Acceleration ramp mm_remaining -= (st_prep_data->current_speed*DT_SEGMENT + pl_prep_block->acceleration*(0.5*DT_SEGMENT*DT_SEGMENT)); if (mm_remaining < st_prep_data->accelerate_until) { // **Incomplete** Acceleration ramp end. // Acceleration-cruise, acceleration-deceleration ramp junction, or end of block. mm_remaining = st_prep_data->accelerate_until; // NOTE: 0.0 at EOB dt = 2*(pl_prep_block->millimeters-mm_remaining)/ (st_prep_data->current_speed+st_prep_data->maximum_speed); st_prep_data->current_speed = st_prep_data->maximum_speed; } else { // **Complete** Acceleration only. st_prep_data->current_speed += pl_prep_block->acceleration*DT_SEGMENT; } } else if (mm_remaining <= st_prep_data->decelerate_after) { // Deceleration ramp mm_remaining -= (st_prep_data->current_speed*DT_SEGMENT - pl_prep_block->acceleration*(0.5*DT_SEGMENT*DT_SEGMENT)); if (mm_remaining > 0.0) { // **Complete** Deceleration only. st_prep_data->current_speed -= pl_prep_block->acceleration*DT_SEGMENT; } else { // **Complete* End of block. dt = 2*pl_prep_block->millimeters/(st_prep_data->current_speed+st_prep_data->exit_speed); mm_remaining = 0.0; // st_prep_data->current_speed = st_prep_data->exit_speed; } } else { // Cruising profile mm_remaining -= st_prep_data->maximum_speed*DT_SEGMENT; if (mm_remaining < st_prep_data->decelerate_after) { // **Incomplete** End of cruise. // Cruise-deceleration junction or end of block. mm_remaining = st_prep_data->decelerate_after; // NOTE: 0.0 at EOB dt = (pl_prep_block->millimeters-mm_remaining)/st_prep_data->maximum_speed; } // Otherwise **Complete** Cruising only. } // ----------------------------------------------------------------------------------- // If segment is incomplete, attempt to fill the remainder. // NOTE: Segment remainder always spans a cruise and/or a deceleration ramp. if (dt < DT_SEGMENT) { if (mm_remaining > 0.0) { // Skip if end of block. float last_mm_remaining; float dt_remainder; // Fill incomplete segment with an acceleration junction. if (mm_remaining > st_prep_data->decelerate_after) { // Cruising profile last_mm_remaining = mm_remaining; dt_remainder = DT_SEGMENT-dt; mm_remaining -= st_prep_data->current_speed*dt_remainder; if (mm_remaining < st_prep_data->decelerate_after) { // **Incomplete** mm_remaining = st_prep_data->decelerate_after; dt += (last_mm_remaining-mm_remaining)/st_prep_data->maximum_speed; // current_speed = maximum_speed; } else { // **Complete** Segment filled. dt = DT_SEGMENT; } } // Fill incomplete segment with a deceleration junction. if (mm_remaining > 0.0) { if (mm_remaining <= st_prep_data->decelerate_after) { // Deceleration ramp last_mm_remaining = mm_remaining; dt_remainder = DT_SEGMENT-dt; mm_remaining -= (dt_remainder*(st_prep_data->current_speed - 0.5*pl_prep_block->acceleration*dt_remainder)); if (mm_remaining > 0.0) { // **Complete** Segment filled. st_prep_data->current_speed -= pl_prep_block->acceleration*dt_remainder; dt = DT_SEGMENT; } else { // **Complete** End of block. mm_remaining = 0.0; dt += (2*last_mm_remaining/(st_prep_data->current_speed+st_prep_data->exit_speed)); // st_prep_data->current_speed = st_prep_data->exit_speed; } } } } } // ----------------------------------------------------------------------------------- // Compute segment step rate, steps to execute, and step phase correction parameters. // Convert segment distance in terms of steps. // float dist_travel = pl_prep_block->millimeters; // if (mm_remaining > 0.0) { dist_travel -= mm_remaining; } if (mm_remaining > 0.0) { float steps_remaining = st_prep_step_per_mm*mm_remaining; prep_segment->dist_per_tick = ceil( (INV_TIME_MULTIPLIER/ISR_TICKS_PER_SECOND)* (st_prep_data->step_events_remaining-steps_remaining)/dt ); // (mult*step/isr_tic) // Compute number of steps to execute and segment step phase correction. prep_segment->n_step = ceil(st_prep_data->step_events_remaining)-ceil(steps_remaining); prep_segment->n_phase_tick = ceil(INV_TIME_MULTIPLIER*(ceil(steps_remaining)-steps_remaining)/prep_segment->dist_per_tick); // Update step execution variables st_prep_data->step_events_remaining = steps_remaining; pl_prep_block->millimeters = mm_remaining; } else { // End of block. Finish it out. prep_segment->dist_per_tick = ceil( (INV_TIME_MULTIPLIER/ISR_TICKS_PER_SECOND)* st_prep_data->step_events_remaining/dt ); // (mult*step/isr_tic) // Set to execute the remaining steps and no phase correction upon finishing the block. prep_segment->n_step = ceil(st_prep_data->step_events_remaining); prep_segment->n_phase_tick = 0; // NOTE: Not required. Planner will ignore this block as it is now complete. // st_prep_data->step_events_remaining = 0.0; // pl_prep_block->millimeters = 0.0; // 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; prep_segment->flag |= SEGMENT_END_OF_BLOCK; } // !!! PROBLEM. Step events remaining in floating point can limit the number of steps // we can accurately track, since floats have ~7.2 significant digits. However, this only // becomes a problem if there are more than 1,000,000, which translates to a CNC machine // with 200 step/mm and 5 meters of axis travel. Possible but unlikely. Could have more // issues with user setting up their machine with too high of steps. // TODO: dist_per_tick must be less than INV_TIME_MULTIPLIER. A check can be made to // make this a hard limit. Need to make sure this doesn't affect the velocity profiles.. // it shouldn't. The same could said for the minimum allowable step rate too. This should // not affect the tracing of the profiles either. // Ensure the initial step rate exceeds the MINIMUM_STEP_RATE. // TODO: Use config.h error checking to do this. Otherwise, counters get screwy. // New step segment initialization completed. Increment segment buffer indices. segment_buffer_head = segment_next_head; if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { segment_next_head = 0; } SPINDLE_ENABLE_PORT ^= 1<entry_speed_sqr = st_prep_data->current_speed*st_prep_data->current_speed; // pl_partial_block->max_entry_speed_sqr = pl_partial_block->entry_speed_sqr; // Not sure if this needs to be updated. // 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; }