/* stepper.c - stepper motor driver: executes motion plans using stepper motors Part of Grbl The MIT License (MIT) GRBL(tm) - Embedded CNC g-code interpreter and motion-controller Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2011-2012 Sungeun K. Jeon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "stepper.h" #include "config.h" #include "settings.h" #include "planner.h" // Some useful constants #define TICKS_PER_MICROSECOND (F_CPU/1000000) #define CYCLES_PER_ACCELERATION_TICK ((TICKS_PER_MICROSECOND*1000000)/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; uint32_t event_count; uint32_t step_events_completed; // The number of step events left in current motion // Used by the trapezoid generator uint32_t cycles_per_step_event; // The number of machine cycles between each step event uint32_t trapezoid_tick_cycle_counter; // The cycles since last trapezoid_tick. Used to generate ticks at a steady // pace without allocating a separate timer uint32_t trapezoid_adjusted_rate; // The current rate of step_events according to the trapezoid generator uint32_t min_safe_rate; // Minimum safe rate for full deceleration rate reduction step. Otherwise halves step_rate. } 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 static volatile uint8_t busy; // True when SIG_OUTPUT_COMPARE1A is being serviced. Used to avoid retriggering that handler. #if STEP_PULSE_DELAY > 0 static uint8_t step_bits; // Stores out_bits output to complete the step pulse delay #endif // __________________________ // /| |\ _________________ ^ // / | | \ /| |\ | // / | | \ / | | \ 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 // during the first block->accelerate_until step_events_completed, then keeps going at constant speed until // step_events_completed reaches block->decelerate_after after which it decelerates until the trapezoid generator is reset. // The slope of acceleration is always +/- block->rate_delta and is applied at a constant rate following the midpoint rule // by the trapezoid generator, which is called ACCELERATION_TICKS_PER_SECOND times per second. static void set_step_events_per_minute(uint32_t steps_per_minute); // 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); // Set delay between direction pin write and step command. OCR2A = -(((settings.pulse_microseconds)*TICKS_PER_MICROSECOND) >> 3); #else // Normal operation // Set step pulse time. Ad hoc computation from oscilloscope. Uses two's complement. step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3); #endif // Enable stepper driver interrupt TIMSK1 |= (1< CYCLES_PER_ACCELERATION_TICK) { st.trapezoid_tick_cycle_counter -= CYCLES_PER_ACCELERATION_TICK; return(true); } else { return(false); } } // "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is executed at the rate set with // config_step_timer. It 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. ISR(TIMER1_COMPA_vect) { if (busy) { return; } // The busy-flag is used to avoid reentering this interrupt // Set the direction pins a couple of nanoseconds before we step the steppers STEPPING_PORT = (STEPPING_PORT & ~DIRECTION_MASK) | (out_bits & DIRECTION_MASK); // Then pulse the stepping pins #ifdef STEP_PULSE_DELAY step_bits = (STEPPING_PORT & ~STEP_MASK) | out_bits; // Store out_bits to prevent overwriting. #else // Normal operation STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | out_bits; #endif // Enable step pulse reset timer so that The Stepper Port Reset Interrupt can reset the signal after // exactly settings.pulse_microseconds microseconds, independent of the main Timer1 prescaler. TCNT2 = step_pulse_time; // Reload timer counter TCCR2B = (1<initial_rate; set_step_events_per_minute(st.trapezoid_adjusted_rate); // Initialize cycles_per_step_event st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK/2; // Start halfway for midpoint rule. } st.min_safe_rate = current_block->rate_delta + (current_block->rate_delta >> 1); // 1.5 x rate_delta 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_completed = 0; } else { st_go_idle(); bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end } } if (current_block != NULL) { // Execute step displacement profile by bresenham line algorithm out_bits = current_block->direction_bits; st.counter_x += current_block->steps_x; if (st.counter_x > 0) { out_bits |= (1<steps_y; if (st.counter_y > 0) { out_bits |= (1<steps_z; if (st.counter_z > 0) { out_bits |= (1<step_event_count) { if (sys.state == STATE_HOLD) { // Check for and execute feed hold by enforcing a steady deceleration from the moment of // execution. The rate of deceleration is limited by rate_delta and will never decelerate // faster or slower than in normal operation. If the distance required for the feed hold // deceleration spans more than one block, the initial rate of the following blocks are not // updated and deceleration is continued according to their corresponding rate_delta. // NOTE: The trapezoid tick cycle counter is not updated intentionally. This ensures that // the deceleration is smooth regardless of where the feed hold is initiated and if the // deceleration distance spans multiple blocks. if ( iterate_trapezoid_cycle_counter() ) { // If deceleration complete, set system flags and shutdown steppers. if (st.trapezoid_adjusted_rate <= current_block->rate_delta) { // Just go idle. Do not NULL current block. The bresenham algorithm variables must // remain intact to ensure the stepper path is exactly the same. Feed hold is still // active and is released after the buffer has been reinitialized. st_go_idle(); bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program that feed hold is complete. } else { st.trapezoid_adjusted_rate -= current_block->rate_delta; set_step_events_per_minute(st.trapezoid_adjusted_rate); } } } else { // The trapezoid generator always checks step event location to ensure de/ac-celerations are // executed and terminated at exactly the right time. This helps prevent over/under-shooting // the target position and speed. // NOTE: By increasing the ACCELERATION_TICKS_PER_SECOND in config.h, the resolution of the // discrete velocity changes increase and accuracy can increase as well to a point. Numerical // round-off errors can effect this, if set too high. This is important to note if a user has // very high acceleration and/or feedrate requirements for their machine. if (st.step_events_completed < current_block->accelerate_until) { // Iterate cycle counter and check if speeds need to be increased. if ( iterate_trapezoid_cycle_counter() ) { st.trapezoid_adjusted_rate += current_block->rate_delta; if (st.trapezoid_adjusted_rate >= current_block->nominal_rate) { // Reached nominal rate a little early. Cruise at nominal rate until decelerate_after. st.trapezoid_adjusted_rate = current_block->nominal_rate; } set_step_events_per_minute(st.trapezoid_adjusted_rate); } } else if (st.step_events_completed >= current_block->decelerate_after) { // Reset trapezoid tick cycle counter to make sure that the deceleration is performed the // same every time. Reset to CYCLES_PER_ACCELERATION_TICK/2 to follow the midpoint rule for // an accurate approximation of the deceleration curve. For triangle profiles, down count // from current cycle counter to ensure exact deceleration curve. if (st.step_events_completed == current_block-> decelerate_after) { if (st.trapezoid_adjusted_rate == current_block->nominal_rate) { st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK/2; // Trapezoid profile } else { st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK-st.trapezoid_tick_cycle_counter; // Triangle profile } } else { // Iterate cycle counter and check if speeds need to be reduced. if ( iterate_trapezoid_cycle_counter() ) { // NOTE: We will only do a full speed reduction if the result is more than the minimum safe // rate, initialized in trapezoid reset as 1.5 x rate_delta. Otherwise, reduce the speed by // half increments until finished. The half increments are guaranteed not to exceed the // CNC acceleration limits, because they will never be greater than rate_delta. This catches // small errors that might leave steps hanging after the last trapezoid tick or a very slow // step rate at the end of a full stop deceleration in certain situations. The half rate // reductions should only be called once or twice per block and create a nice smooth // end deceleration. if (st.trapezoid_adjusted_rate > st.min_safe_rate) { st.trapezoid_adjusted_rate -= current_block->rate_delta; } else { st.trapezoid_adjusted_rate >>= 1; // Bit shift divide by 2 } if (st.trapezoid_adjusted_rate < current_block->final_rate) { // Reached final rate a little early. Cruise to end of block at final rate. st.trapezoid_adjusted_rate = current_block->final_rate; } set_step_events_per_minute(st.trapezoid_adjusted_rate); } } } else { // No accelerations. Make sure we cruise exactly at the nominal rate. if (st.trapezoid_adjusted_rate != current_block->nominal_rate) { st.trapezoid_adjusted_rate = current_block->nominal_rate; set_step_events_per_minute(st.trapezoid_adjusted_rate); } } } } else { // If current block is finished, reset pointer current_block = NULL; plan_discard_current_block(); } } out_bits ^= settings.invert_mask; // Apply step and direction invert mask busy = false; } // This interrupt is set up by ISR_TIMER1_COMPAREA when it sets the motor port bits. It resets // the motor port after a short period (settings.pulse_microseconds) completing one step cycle. // NOTE: Interrupt collisions between the serial and stepper interrupts can cause delays by // a few microseconds, if they execute right before one another. Not a big deal, but can // cause issues at high step rates if another high frequency asynchronous interrupt is // added to Grbl. ISR(TIMER2_OVF_vect) { // Reset stepping pins (leave the direction pins) STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.invert_mask & STEP_MASK); TCCR2B = 0; // Disable Timer2 to prevent re-entering this interrupt when it's not needed. } #ifdef STEP_PULSE_DELAY // This interrupt is used only when STEP_PULSE_DELAY is enabled. Here, the step pulse is // initiated after the STEP_PULSE_DELAY time period has elapsed. The ISR TIMER2_OVF interrupt // will then trigger after the appropriate settings.pulse_microseconds, as in normal operation. // The new timing between direction, step pulse, and step complete events are setup in the // st_wake_up() routine. ISR(TIMER2_COMPA_vect) { STEPPING_PORT = step_bits; // Begin step pulse. } #endif // Reset and clear stepper subsystem variables void st_reset() { memset(&st, 0, sizeof(st)); set_step_events_per_minute(MINIMUM_STEPS_PER_MINUTE); 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<> 3; prescaler = 2; // prescaler: 8 actual_cycles = ceiling * 8L; } else if (cycles <= 0x3fffffL) { ceiling = cycles >> 6; prescaler = 3; // prescaler: 64 actual_cycles = ceiling * 64L; } else if (cycles <= 0xffffffL) { ceiling = (cycles >> 8); prescaler = 4; // prescaler: 256 actual_cycles = ceiling * 256L; } else if (cycles <= 0x3ffffffL) { ceiling = (cycles >> 10); prescaler = 5; // prescaler: 1024 actual_cycles = ceiling * 1024L; } else { // Okay, that was slower than we actually go. Just set the slowest speed ceiling = 0xffff; prescaler = 5; actual_cycles = 0xffff * 1024; } // Set prescaler TCCR1B = (TCCR1B & ~(0x07<step_event_count - st.step_events_completed); // Update initial rate and timers after feed hold. st.trapezoid_adjusted_rate = 0; // Resumes from rest set_step_events_per_minute(st.trapezoid_adjusted_rate); st.trapezoid_tick_cycle_counter = CYCLES_PER_ACCELERATION_TICK/2; // Start halfway for midpoint rule. st.step_events_completed = 0; sys.state = STATE_QUEUED; } else { sys.state = STATE_IDLE; } }