Updates to edge/dev. Line buffer increased/planner buffer decreased. Line overflow feedback.

- Increased g-code parser line buffer to 70 characters (from 50) to
prevent some long arc commands from getting truncated.

- Decreased planner buffer from 18 to 17 blocks to free up memory for
line buffer.

- Added a line buffer overflow feedback error (Thanks @BHSPitMonkey!)
This commit is contained in:
Sonny Jeon 2013-04-05 09:21:52 -06:00
parent 08baabc63c
commit 1fa3dad206
9 changed files with 454 additions and 15 deletions

View File

@ -210,7 +210,7 @@
// available RAM, like when re-compiling for a Mega or Sanguino. Or decrease if the Arduino // available RAM, like when re-compiling for a Mega or Sanguino. Or decrease if the Arduino
// begins to crash due to the lack of available RAM or if the CPU is having trouble keeping // begins to crash due to the lack of available RAM or if the CPU is having trouble keeping
// up with planning new incoming motions as they are executed. // up with planning new incoming motions as they are executed.
// #define BLOCK_BUFFER_SIZE 18 // Uncomment to override default in planner.h. // #define BLOCK_BUFFER_SIZE 17 // Uncomment to override default in planner.h.
// Line buffer size from the serial input stream to be executed. Also, governs the size of // Line buffer size from the serial input stream to be executed. Also, governs the size of
// each of the startup blocks, as they are each stored as a string of this size. Make sure // each of the startup blocks, as they are each stored as a string of this size. Make sure
@ -220,7 +220,7 @@
// can be too small and g-code blocks can get truncated. Officially, the g-code standards // can be too small and g-code blocks can get truncated. Officially, the g-code standards
// support up to 256 characters. In future versions, this default will be increased, when // support up to 256 characters. In future versions, this default will be increased, when
// we know how much extra memory space we can re-invest into this. // we know how much extra memory space we can re-invest into this.
// #define LINE_BUFFER_SIZE 50 // Uncomment to override default in protocol.h // #define LINE_BUFFER_SIZE 70 // Uncomment to override default in protocol.h
// Serial send and receive buffer size. The receive buffer is often used as another streaming // Serial send and receive buffer size. The receive buffer is often used as another streaming
// buffer to store incoming blocks to be processed by Grbl when its ready. Most streaming // buffer to store incoming blocks to be processed by Grbl when its ready. Most streaming

View File

@ -245,6 +245,7 @@ uint8_t gc_execute_line(char *line)
// If there were any errors parsing this line, we will return right away with the bad news // If there were any errors parsing this line, we will return right away with the bad news
if (gc.status_code) { return(gc.status_code); } if (gc.status_code) { return(gc.status_code); }
uint8_t i;
/* Execute Commands: Perform by order of execution defined in NIST RS274-NGC.v3, Table 8, pg.41. */ /* Execute Commands: Perform by order of execution defined in NIST RS274-NGC.v3, Table 8, pg.41. */
@ -290,7 +291,6 @@ uint8_t gc_execute_line(char *line)
else { int_value = gc.coord_select; } // Index P0 as the active coordinate system else { int_value = gc.coord_select; } // Index P0 as the active coordinate system
float coord_data[N_AXIS]; float coord_data[N_AXIS];
if (!settings_read_coord_data(int_value,coord_data)) { return(STATUS_SETTING_READ_FAIL); } if (!settings_read_coord_data(int_value,coord_data)) { return(STATUS_SETTING_READ_FAIL); }
uint8_t i;
// Update axes defined only in block. Always in machine coordinates. Can change non-active system. // Update axes defined only in block. Always in machine coordinates. Can change non-active system.
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used. for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used.
if (bit_istrue(axis_words,bit(i)) ) { if (bit_istrue(axis_words,bit(i)) ) {
@ -312,7 +312,6 @@ uint8_t gc_execute_line(char *line)
// and absolute and incremental modes. // and absolute and incremental modes.
if (axis_words) { if (axis_words) {
// Apply absolute mode coordinate offsets or incremental mode offsets. // Apply absolute mode coordinate offsets or incremental mode offsets.
uint8_t i;
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used. for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used.
if ( bit_istrue(axis_words,bit(i)) ) { if ( bit_istrue(axis_words,bit(i)) ) {
if (gc.absolute_mode) { if (gc.absolute_mode) {
@ -350,7 +349,6 @@ uint8_t gc_execute_line(char *line)
} else { } else {
// Update axes defined only in block. Offsets current system to defined value. Does not update when // Update axes defined only in block. Offsets current system to defined value. Does not update when
// active coordinate system is selected, but is still active unless G92.1 disables it. // active coordinate system is selected, but is still active unless G92.1 disables it.
uint8_t i;
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used. for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used.
if (bit_istrue(axis_words,bit(i)) ) { if (bit_istrue(axis_words,bit(i)) ) {
gc.coord_offset[i] = gc.position[i]-gc.coord_system[i]-target[i]; gc.coord_offset[i] = gc.position[i]-gc.coord_system[i]-target[i];
@ -385,7 +383,6 @@ uint8_t gc_execute_line(char *line)
// Convert all target position data to machine coordinates for executing motion. Apply // Convert all target position data to machine coordinates for executing motion. Apply
// absolute mode coordinate offsets or incremental mode offsets. // absolute mode coordinate offsets or incremental mode offsets.
// NOTE: Tool offsets may be appended to these conversions when/if this feature is added. // NOTE: Tool offsets may be appended to these conversions when/if this feature is added.
uint8_t i;
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used to save flash space. for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used to save flash space.
if ( bit_istrue(axis_words,bit(i)) ) { if ( bit_istrue(axis_words,bit(i)) ) {
if (!absolute_override) { // Do not update target in absolute override mode if (!absolute_override) { // Do not update target in absolute override mode

View File

@ -24,7 +24,7 @@
// The number of linear motions that can be in the plan at any give time // The number of linear motions that can be in the plan at any give time
#ifndef BLOCK_BUFFER_SIZE #ifndef BLOCK_BUFFER_SIZE
#define BLOCK_BUFFER_SIZE 18 #define BLOCK_BUFFER_SIZE 17
#endif #endif
// This struct is used when buffering the setup for each linear movement "nominal" values are as specified in // This struct is used when buffering the setup for each linear movement "nominal" values are as specified in

View File

@ -37,10 +37,16 @@ static uint8_t char_counter; // Last character counter in line variable.
static uint8_t iscomment; // Comment/block delete flag for processor to ignore comment characters. static uint8_t iscomment; // Comment/block delete flag for processor to ignore comment characters.
static void protocol_reset_line_buffer()
{
char_counter = 0;
iscomment = false;
}
void protocol_init() void protocol_init()
{ {
char_counter = 0; // Reset line input protocol_reset_line_buffer(); // Reset line input
iscomment = false;
report_init_message(); // Welcome message report_init_message(); // Welcome message
PINOUT_DDR &= ~(PINOUT_MASK); // Set as input pins PINOUT_DDR &= ~(PINOUT_MASK); // Set as input pins
@ -49,6 +55,7 @@ void protocol_init()
PCICR |= (1 << PINOUT_INT); // Enable Pin Change Interrupt PCICR |= (1 << PINOUT_INT); // Enable Pin Change Interrupt
} }
// Executes user startup script, if stored. // Executes user startup script, if stored.
void protocol_execute_startup() void protocol_execute_startup()
{ {
@ -65,6 +72,7 @@ void protocol_execute_startup()
} }
} }
// Pin change interrupt for pin-out commands, i.e. cycle start, feed hold, and reset. Sets // Pin change interrupt for pin-out commands, i.e. cycle start, feed hold, and reset. Sets
// only the runtime command execute variable to have the main program execute these when // only the runtime command execute variable to have the main program execute these when
// its ready. This works exactly like the character-based runtime commands when picked off // its ready. This works exactly like the character-based runtime commands when picked off
@ -305,8 +313,7 @@ void protocol_process()
// Empty or comment line. Skip block. // Empty or comment line. Skip block.
report_status_message(STATUS_OK); // Send status message for syncing purposes. report_status_message(STATUS_OK); // Send status message for syncing purposes.
} }
char_counter = 0; // Reset line buffer index protocol_reset_line_buffer();
iscomment = false; // Reset comment flag
} else { } else {
if (iscomment) { if (iscomment) {
@ -324,7 +331,9 @@ void protocol_process()
// Enable comments flag and ignore all characters until ')' or EOL. // Enable comments flag and ignore all characters until ')' or EOL.
iscomment = true; iscomment = true;
} else if (char_counter >= LINE_BUFFER_SIZE-1) { } else if (char_counter >= LINE_BUFFER_SIZE-1) {
// Throw away any characters beyond the end of the line buffer // Detect line buffer overflow. Report error and reset line buffer.
report_status_message(STATUS_OVERFLOW);
protocol_reset_line_buffer();
} else if (c >= 'a' && c <= 'z') { // Upcase lowercase } else if (c >= 'a' && c <= 'z') { // Upcase lowercase
line[char_counter++] = c-'a'+'A'; line[char_counter++] = c-'a'+'A';
} else { } else {

View File

@ -30,7 +30,7 @@
// memory space we can invest into here or we re-write the g-code parser not to have his // memory space we can invest into here or we re-write the g-code parser not to have his
// buffer. // buffer.
#ifndef LINE_BUFFER_SIZE #ifndef LINE_BUFFER_SIZE
#define LINE_BUFFER_SIZE 50 #define LINE_BUFFER_SIZE 70
#endif #endif
// Initialize the serial protocol // Initialize the serial protocol

View File

@ -76,6 +76,8 @@ void report_status_message(uint8_t status_code)
printPgmString(PSTR("Alarm lock")); break; printPgmString(PSTR("Alarm lock")); break;
case STATUS_SOFT_LIMIT_ERROR: case STATUS_SOFT_LIMIT_ERROR:
printPgmString(PSTR("Homing not enabled")); break; printPgmString(PSTR("Homing not enabled")); break;
case STATUS_OVERFLOW:
printPgmString(PSTR("Line overflow")); break;
} }
printPgmString(PSTR("\r\n")); printPgmString(PSTR("\r\n"));
} }

View File

@ -36,6 +36,7 @@
#define STATUS_IDLE_ERROR 11 #define STATUS_IDLE_ERROR 11
#define STATUS_ALARM_LOCK 12 #define STATUS_ALARM_LOCK 12
#define STATUS_SOFT_LIMIT_ERROR 13 #define STATUS_SOFT_LIMIT_ERROR 13
#define STATUS_OVERFLOW 14
// Define Grbl alarm codes. Less than zero to distinguish alarm error from status error. // Define Grbl alarm codes. Less than zero to distinguish alarm error from status error.
#define ALARM_LIMIT_ERROR -1 #define ALARM_LIMIT_ERROR -1

View File

@ -78,6 +78,7 @@ static volatile uint8_t busy; // True when "Stepper Driver Interrupt" is being
// after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as // 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. // +/- 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.
void st_wake_up() void st_wake_up()
@ -101,6 +102,7 @@ void st_wake_up()
} }
} }
// Stepper shutdown // Stepper shutdown
void st_go_idle() void st_go_idle()
{ {
@ -297,6 +299,7 @@ ISR(TIMER2_COMPA_vect)
// 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 pulse. This should always trigger before the next Timer2 COMPA interrupt and independently // step 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.
@ -315,6 +318,7 @@ void st_reset()
busy = false; busy = false;
} }
// Initialize and start the stepper motor subsystem // Initialize and start the stepper motor subsystem
void st_init() void st_init()
{ {
@ -352,6 +356,7 @@ void st_cycle_start()
} }
} }
// Execute a feed hold with deceleration, only during cycle. Called by main program. // Execute a feed hold with deceleration, only during cycle. Called by main program.
void st_feed_hold() void st_feed_hold()
{ {
@ -361,6 +366,7 @@ void st_feed_hold()
} }
} }
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by // 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. // 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 // NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper

424
stepper_new.c Normal file
View File

@ -0,0 +1,424 @@
/*
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/>.
*/
#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;
uint32_t event_count; // Total event count. Retained for feed holds.
uint32_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;
TCNT0 = 0; // Clear Timer2
TIMSK0 |= (1<<OCIE0A); // Enable Timer0 Compare Match A interrupt
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
}
}
// Stepper shutdown
void st_go_idle()
{
// Disable stepper driver interrupt. Allow Timer2 to finish. It will disable itself.
TIMSK0 &= ~(1<<OCIE0A); // Disable Timer0 interrupt
TCCR0B = 0; // Disable Timer0
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/cause a phasing drift error between multiple axes.
Grbl's algorithm differs by using a single Ranade-type 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 and always in phase by inherent algorithm design. 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(TIMER0_COMPA_vect)
{
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
// 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;
TCNT2 = step_pulse_time; // Reload Timer2 counter.
TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler
}
// Assume that this takes less than 5 usec. If not, then this might not work on an 328p.
// Two sei() commands in two different interrupts will be hard to manage. If the main program
// can push fast enough, then this might be ok.
// sei(); // ??? The falling edge interrupt needs to fire before the rest of this executes.
/*
1. Upon start, load segment/block.
- Set direction bit for entire block early. This never changes.
- Load Bresenham variables. Initialize their counters.
- If using segments, counters cannot be updated, but this breaks the direction bit? No. Only set when block begins.
(3) Generate step event. Can take up to an additional 10-15usec for the math.
Override idea: Main program can request the step event count from the stepper algorithm, which will
check for the request and write it to a safe variable for the main program. The main program will
then wait until the request is fulfilled via a flag. From there, the main program can determine
the safe point from which it can plan. This may require a snapshot of variables. Hopefully this
won't take too much time in the interrupt.
*/
// Iterate 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; }
// Prepare Bresenham step event, when it's time to do so.
if (st.d_counter < 0) {
st.d_counter += current_block->d_next;
// Load next step
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;
if (st.counter_x < 0) {
out_bits |= (1<<X_STEP_BIT);
st.counter_x += st.event_count;
}
st.counter_y -= current_block->steps_y;
if (st.counter_y < 0) {
out_bits |= (1<<Y_STEP_BIT);
st.counter_y += st.event_count;
}
st.counter_z -= current_block->steps_z;
if (st.counter_z < 0) {
out_bits |= (1<<Z_STEP_BIT);
st.counter_z += st.event_count;
}
// Check step events for trapezoid change or end of block.
st.step_events_remaining--; // Decrement step events count
if (st.step_events_remaining == 0) {
// Load next line motion
}
out_bits ^= settings.invert_mask; // Apply step port invert mask
// TIMSK2 |= (1<<OCIE2B); // Enable Timer2 Compare Match B interrupt
}
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT;
}
// This needs to complete and load before the next timer?
ISR(TIMER0_COMPB_vect)
{
if (busy) { return; }
busy = true;
TIMSK0 &= ~(1<<OCIE0B); // Disable Timer2 Compare Match B interrupt
sei();
if (out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; }
// If current block is finished, reset pointer
current_block = NULL;
plan_discard_current_block();
// 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 inverse time 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. Also, this
// allows the direction bits for the block to be always set one timer tick before the
// first step event.
// 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 inverse time 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.
}
}
}
}
// 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;
}
}
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 {
busy = false;
}
// The Stepper Port Reset Interrupt: Timer2 OVF interrupt handles the falling edge of the
// step pulse. This should always trigger before the next Timer0 COMPA interrupt and independently
// finish, if Timer0 is disabled after completing a move.
ISR(TIMER2_OVF_vect)
{
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.invert_mask & STEP_MASK);
TCCR2B = 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 0
TIMSK0 &= ~(1<<OCIE0A); // Disable Timer0 interrupt while configuring it
TCCR0B = 0; // Disable Timer2 until needed
TCNT0 = 0; // Clear Timer2 counter
TCCR0A = (1<<WGM21); // Set CTC mode
OCR0A = (F_CPU/ISR_TICKS_PER_SECOND)/8 - 1; // Set Timer2 CTC rate
// Configure Timer 2
TIMSK2 &= ~(1<<TOIE2);
TCCR2A = 0; // Normal operation
TCCR2B = 0; // Disable Timer2 until needed
TIMSK2 |= (1<<TOIE2); // 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;
}
}