diff --git a/README.md b/README.md index c58f3a1..119ef70 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,20 @@ Grbl includes full acceleration management with look ahead. That means the contr ##Changelog for v0.9 from v0.8 - **ALPHA status: Under heavy development.** - - New stepper algorithm: Based on an inverse time algorithm, but modified to ensure steps are executed exactly. This algorithm performs a constant timer tick and has a hard limit of 30kHz maximum step frequency. It is also highly tuneable and should be very easy to port to other microcontroller architectures. Overall, a much better, smoother stepper algorithm with the capability of very high speeds. - - Planner optimizations: Planning computations improved four-fold or more. Changes include streaming optimizations by ignoring already optimized blocks and removing redundant variables and computations and offloading them to the stepper algorithm on an ad-hoc basis. + - New stepper algorithm: Complete overhaul of the handling of the stepper driver to simplify and reduce task time per ISR tick. Much smoother operation with the new Adaptive Multi-Axis Step Smoothing (AMASS) algorithm which does what its name implies. Users should audibly hear significant differences in how their machines move and see improved overall performance! + - Planner optimizations: Planning computations improved four-fold or more by optimizing end-to-end operations. Changes include streaming optimizations by ignoring already optimized blocks and removing redundant variables and computations and offloading them to the stepper algorithm to compute on an ad-hoc basis. - Planner stability: Previous Grbl planners have all had a corruption issue in rare circumstances that becomes particularly problematic at high step frequencies and when jogging. The new planner is robust and incorruptible, meaning that we can fearlessly drive Grbl to it's highest limits. Combined with the new stepper algorithm and planner optimizations, this means 5x to 10x performance increases in our testing! This is all achieved through the introduction of an intermediary step segment buffer that "checks-out" steps from the planner buffer in real-time. - Acceleration independence: Each axes may be defined with different acceleration parameters and Grbl will automagically calculate the maximum acceleration through a path depending on the direction traveled. This is very useful for machine that have very different axes properties, like the ShapeOko z-axis. - Maximum velocity independence: As with acceleration, the maximum velocity of individual axes may be defined. All seek/rapids motions will move at these maximum rates, but never exceed any one axes. So, when two or more axes move, the limiting axis will move at its maximum rate, while the other axes are scaled down. - - Significantly improved arc performance: Arcs are now defined in terms of chordal tolerance, rather than segment length. Chordal tolerance will automatically scale all arc line segments depending on arc radius, such that the error does not exceed the tolerance value (default: 0.005 mm.) So, for larger radii arcs, Grbl can move faster through them, because the segments are always longer and the planner has more distance to plan with. + - Significantly improved arc performance: Arcs are now defined in terms of chordal tolerance, rather than segment length. Chordal tolerance will automatically scale all arc line segments depending on arc radius, such that the error does not exceed the tolerance value (default: 0.002 mm.) So, for larger radii arcs, Grbl can move faster through them, because the segments are always longer and the planner has more distance to plan with. - Soft limits: Checks if any motion command exceeds workspace limits. Alarms out when found. Another safety feature, but, unlike hard limits, position does not get lost, as it forces a feed hold before erroring out. - - Pin mapping: In an effort for Grbl to be compatible with other AVR architectures, such as the 1280 or 2560, a new pin_map.h configuration file has been created to allow Grbl to be compiled for them. This is currently user supported, so your mileage may vary. If you run across a bug, please let us know or better send us a fix! Thanks in advance! + - CPU pin mapping: In an effort for Grbl to be compatible with other AVR architectures, such as the 1280 or 2560, a new cpu_map.h pin configuration file has been created to allow Grbl to be compiled for them. This is currently user supported, so your mileage may vary. If you run across a bug, please let us know or better send us a fix! Thanks in advance! - New Grbl SIMULATOR by @jgeisler: A completely independent wrapper of the Grbl main source code that may be compiled as an executable on a computer. No Arduino required. Simply simulates the responses of Grbl as if it was on an Arduino. May be used for many things: checking out how Grbl works, pre-process moves for GUI graphics, debugging of new features, etc. Much left to do, but potentially very powerful, as the dummy AVR variables can be written to output anything you need. - Homing routine updated: Sets workspace volume in all negative space regardless of limit switch position. Common on pro CNCs. Now tied directly into the main planner and stepper modules to reduce flash space and allow maximum speeds during seeking. - - Feedrate overrides: In the works, but planner has begun to be re-factored for this feature. - - Jogging controls: Methodology needs to be to figured out first. Could be dropped due to flash space concerns. Last item on the agenda. + - Combined limit pins capability: Limit switches can be combined to share the same pins to free up precious I/O pins for other purposes. When combined, users must adjust the homing cycle mask in config.h to not home the axes on a shared pin at the same time. + - Variable spindle speed output: Available only as a compile-time option through the config.h file. Enables PWM output for 'S' g-code commands. Enabling this feature will swap the Z-limit D11 pin and spindle enable D12 pin to access the hardware PWM on pin D12. The Z-limit pin, now on D12, should work just as it did before. + - Increased serial baud rate: Default serial baudrate is now 115200, because the new planner and stepper allows much higher processing and execution speeds that 9600 baud was just not cutting it. + - Feedrate overrides: (Slated for v1.0 release) The framework to enable feedrate overrides is in-place with v0.9, but the minor details has not yet been installed. + - Jogging controls: (Slated for v1.0 release) Methodology needs to be to figured out first. Could be dropped due to flash space concerns. _The project was initially inspired by the Arduino GCode Interpreter by Mike Ellery_ diff --git a/config.h b/config.h index dbf37e3..84eff6f 100644 --- a/config.h +++ b/config.h @@ -54,19 +54,24 @@ // mainly a safety feature to remind the user to home, since position is unknown to Grbl. #define HOMING_INIT_LOCK // Comment to disable -// Define the homing cycle search patterns with bitmasks. The homing cycle first performs a search -// to engage the limit switches. HOMING_SEARCH_CYCLE_x are executed in order starting with suffix 0 -// and searches the enabled axes in the bitmask. This allows for users with non-standard cartesian -// machines, such as a lathe (x then z), to configure the homing cycle behavior to their needs. -// Search cycle 0 is required, but cycles 1 and 2 are both optional and may be commented to disable. -// After the search cycle, homing then performs a series of locating about the limit switches to hone -// in on machine zero, followed by a pull-off maneuver. HOMING_LOCATE_CYCLE governs these final moves, -// and this mask must contain all axes in the search. -// NOTE: Later versions may have this installed in settings. -#define HOMING_SEARCH_CYCLE_0 (1< 1. + + void limits_init() { LIMIT_DDR &= ~(LIMIT_MASK); // Set as input pins @@ -65,24 +68,38 @@ void limits_disable() // limit switch can cause a lot of problems, like false readings and multiple interrupt calls. // If a switch is triggered at all, something bad has happened and treat it as such, regardless // if a limit switch is being disengaged. It's impossible to reliably tell the state of a -// bouncing pin without a debouncing method. +// bouncing pin without a debouncing method. A simple software debouncing feature may be enabled +// through the config.h file, where an extra timer delays the limit pin read by several milli- +// seconds to help with, not fix, bouncing switches. // NOTE: Do not attach an e-stop to the limit pins, because this interrupt is disabled during // homing cycles and will not respond correctly. Upon user request or need, there may be a // special pinout for an e-stop, but it is generally recommended to just directly connect // your e-stop switch to the Arduino reset pin, since it is the most correct way to do this. -#ifdef ENABLE_SOFTWARE_DEBOUNCE - ISR(LIMIT_INT_vect) { if (!(WDTCSR & (1< settings.max_travel[Y_AXIS]) { max_travel = settings.max_travel[Y_AXIS]; } if (max_travel > settings.max_travel[Z_AXIS]) { max_travel = settings.max_travel[Z_AXIS]; } - max_travel *= -1.25; // Ensure homing switches engaged by over-estimating max travel. - if (!approach) { max_travel = -max_travel; } - - // Set target location and rate for active axes. - float target[N_AXIS]; - uint8_t n_active_axis = 0; - uint8_t i; - for (i=0; i 0); + + // The active cycle axes should now be homed and machine limits have been located. By + // default, grbl defines machine space as all negative, as do most CNCs. Since limit switches + // can be on either side of an axes, check and set axes machine zero appropriately. Also, + // set up pull-off maneuver from axes limit switches that have been homed. This provides + // some initial clearance off the switches and should also help prevent them from falsely + // triggering when hard limits are enabled or when more than one axes shares a limit pin. + for (idx=0; idx 0) { // NOTE: Check and execute runtime commands during dwell every <= DWELL_TIME_STEP milliseconds. protocol_execute_runtime(); @@ -213,93 +213,37 @@ void mc_homing_cycle() { sys.state = STATE_HOMING; // Set system state variable limits_disable(); // Disable hard limits pin change register for cycle duration - plan_reset(); // Reset planner buffer before beginning homing routine. - st_reset(); // Reset step segment buffer before beginning homing routine. // ------------------------------------------------------------------------------------- // Perform homing routine. NOTE: Special motion case. Only system reset works. // Search to engage all axes limit switches at faster homing seek rate. - limits_go_home(HOMING_SEARCH_CYCLE_0, true, settings.homing_seek_rate); // Search cycle 0 - #ifdef HOMING_SEARCH_CYCLE_1 - limits_go_home(HOMING_SEARCH_CYCLE_1, true, settings.homing_seek_rate); // Search cycle 1 + limits_go_home(HOMING_CYCLE_0); // Homing cycle 0 + #ifdef HOMING_CYCLE_1 + limits_go_home(HOMING_CYCLE_1); // Homing cycle 1 #endif - #ifdef HOMING_SEARCH_CYCLE_2 - limits_go_home(HOMING_SEARCH_CYCLE_2, true, settings.homing_seek_rate); // Search cycle 2 + #ifdef HOMING_CYCLE_2 + limits_go_home(HOMING_CYCLE_2); // Homing cycle 2 #endif - - // Now in proximity of all limits. Carefully leave and approach switches in multiple cycles - // to precisely hone in on the machine zero location. Moves at slower homing feed rate. - int8_t n_cycle = N_HOMING_LOCATE_CYCLE; - while (n_cycle--) { - // Leave all switches to release them. After cycles complete, this is machine zero. - limits_go_home(HOMING_LOCATE_CYCLE, false, settings.homing_feed_rate); - - if (n_cycle > 0) { - // Re-approach all switches to re-engage them. - limits_go_home(HOMING_LOCATE_CYCLE, true, settings.homing_feed_rate); - } - } - // ------------------------------------------------------------------------------------- protocol_execute_runtime(); // Check for reset and set system abort. if (sys.abort) { return; } // Did not complete. Alarm state set by mc_alarm. - // The machine should now be homed and machine limits have been located. By default, - // grbl defines machine space as all negative, as do most CNCs. Since limit switches - // can be on either side of an axes, check and set machine zero appropriately. - // At the same time, set up pull-off maneuver from axes limit switches that have been homed. - // This provides some initial clearance off the switches and should also help prevent them - // from falsely tripping when hard limits are enabled. - float pulloff_target[N_AXIS]; - clear_vector_float(pulloff_target); // Zero pulloff target. - clear_vector_long(sys.position); // Zero current position for now. - uint8_t idx; - for (idx=0; idxmax_junction_speed_sqr = max( MINIMUM_JUNCTION_SPEED*MINIMUM_JUNCTION_SPEED, (block->acceleration * settings.junction_deviation * sin_theta_d2)/(1.0-sin_theta_d2) ); } diff --git a/planner.h b/planner.h index 99298ed..535291e 100644 --- a/planner.h +++ b/planner.h @@ -80,7 +80,4 @@ void plan_cycle_reinitialize(); // Returns the status of the block ring buffer. True, if buffer is full. uint8_t plan_check_full_buffer(); -// Block until all buffered steps are executed -void plan_synchronize(); - #endif diff --git a/protocol.c b/protocol.c index ade1df0..7c7ae2c 100644 --- a/protocol.c +++ b/protocol.c @@ -1,5 +1,5 @@ /* - protocol.c - controls Grbl execution procedures + protocol.c - controls Grbl execution protocol and procedures Part of Grbl Copyright (c) 2011-2014 Sungeun K. Jeon @@ -24,6 +24,7 @@ #include "settings.h" #include "protocol.h" #include "gcode.h" +#include "planner.h" #include "stepper.h" #include "motion_control.h" #include "report.h" @@ -32,95 +33,6 @@ static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. -// Executes run-time commands, when required. This is called from various check points in the main -// program, primarily where there may be a while loop waiting for a buffer to clear space or any -// point where the execution time from the last check point may be more than a fraction of a second. -// This is a way to execute runtime commands asynchronously (aka multitasking) with grbl's g-code -// parsing and planning functions. This function also serves as an interface for the interrupts to -// set the system runtime flags, where only the main program handles them, removing the need to -// define more computationally-expensive volatile variables. This also provides a controlled way to -// execute certain tasks without having two or more instances of the same task, such as the planner -// recalculating the buffer upon a feedhold or override. -// NOTE: The sys.execute variable flags are set by any process, step or serial interrupts, pinouts, -// limit switches, or the main program. -void protocol_execute_runtime() -{ - // Reload step segment buffer - st_prep_buffer(); - - if (sys.execute) { // Enter only if any bit flag is true - uint8_t rt_exec = sys.execute; // Avoid calling volatile multiple times - - // System alarm. Everything has shutdown by something that has gone severely wrong. Report - // the source of the error to the user. If critical, Grbl disables by entering an infinite - // loop until system reset/abort. - if (rt_exec & (EXEC_ALARM | EXEC_CRIT_EVENT)) { - sys.state = STATE_ALARM; // Set system alarm state - - // Critical event. Only hard/soft limit errors currently qualify. - if (rt_exec & EXEC_CRIT_EVENT) { - report_alarm_message(ALARM_LIMIT_ERROR); - report_feedback_message(MESSAGE_CRITICAL_EVENT); - bit_false(sys.execute,EXEC_RESET); // Disable any existing reset - do { - // Nothing. Block EVERYTHING until user issues reset or power cycles. Hard limits - // typically occur while unattended or not paying attention. Gives the user time - // to do what is needed before resetting, like killing the incoming stream. The - // same could be said about soft limits. While the position is not lost, the incoming - // stream could be still engaged and cause a serious crash if it continues afterwards. - } while (bit_isfalse(sys.execute,EXEC_RESET)); - - // Standard alarm event. Only abort during motion qualifies. - } else { - // Runtime abort command issued during a cycle, feed hold, or homing cycle. Message the - // user that position may have been lost and set alarm state to enable the alarm lockout - // to indicate the possible severity of the problem. - report_alarm_message(ALARM_ABORT_CYCLE); - } - bit_false(sys.execute,(EXEC_ALARM | EXEC_CRIT_EVENT)); - } - - // Execute system abort. - if (rt_exec & EXEC_RESET) { - sys.abort = true; // Only place this is set true. - return; // Nothing else to do but exit. - } - - // Execute and serial print status - if (rt_exec & EXEC_STATUS_REPORT) { - report_realtime_status(); - bit_false(sys.execute,EXEC_STATUS_REPORT); - } - - // Initiate stepper feed hold - if (rt_exec & EXEC_FEED_HOLD) { - // !!! During a cycle, the segment buffer has just been reloaded and full. So the math involved - // with the feed hold should be fine for most, if not all, operational scenarios. - st_feed_hold(); // Initiate feed hold. - bit_false(sys.execute,EXEC_FEED_HOLD); - } - - // Reinitializes the stepper module running state and, if a feed hold, re-plans the buffer. - // NOTE: EXEC_CYCLE_STOP is set by the stepper subsystem when a cycle or feed hold completes. - if (rt_exec & EXEC_CYCLE_STOP) { - st_cycle_reinitialize(); - bit_false(sys.execute,EXEC_CYCLE_STOP); - } - - if (rt_exec & EXEC_CYCLE_START) { - st_cycle_start(); // Issue cycle start command to stepper subsystem - if (bit_istrue(settings.flags,BITFLAG_AUTO_START)) { - sys.auto_start = true; // Re-enable auto start after feed hold. - } - bit_false(sys.execute,EXEC_CYCLE_START); - } - } - - // Overrides flag byte (sys.override) and execution should be installed here, since they - // are runtime and require a direct and controlled interface to the main stepper program. -} - - // Directs and executes one line of formatted input from protocol_process. While mostly // incoming streaming g-code blocks, this also directs and executes Grbl internal commands, // such as settings, initiating the homing cycle, and toggling switch states. @@ -141,20 +53,27 @@ static void protocol_execute_line(char *line) status = system_execute_line(line); } else { - // Everything else is gcode. Send to g-code parser! + // Everything else is gcode. Send to g-code parser! Block if in alarm mode. + if (sys.state == STATE_ALARM) { status = STATUS_ALARM_LOCK; } + else { status = gc_execute_line(line); } + // TODO: Separate the parsing from the g-code execution. Need to re-write the parser // completely to do this. First parse the line completely, checking for modal group // errors and storing all of the g-code words. Then, send the stored g-code words to // a separate g-code executor. This will be more in-line with actual g-code protocol. - status = gc_execute_line(line); - + + // TODO: Clean up the multi-tasking workflow with the execution of commands. It's a + // bit complicated and patch-worked. Could be made simplier to understand. } report_status_message(status); } -void protocol_process() +/* + GRBL MAIN LOOP: +*/ +void protocol_main_loop() { // ------------------------------------------------------------ // Complete initialization procedures upon a power-up or reset. @@ -218,15 +137,149 @@ void protocol_process() } } - protocol_execute_runtime(); // Runtime command check point. - if (sys.abort) { return; } // Bail to main() program loop to reset system. - // If there are no more characters in the serial read buffer to be processed and executed, // this indicates that g-code streaming has either filled the planner buffer or has // completed. In either case, auto-cycle start, if enabled, any queued moves. - mc_auto_cycle_start(); - + protocol_auto_cycle_start(); + + protocol_execute_runtime(); // Runtime command check point. + if (sys.abort) { return; } // Bail to main() program loop to reset system. + } return; /* Never reached */ } + + +// Executes run-time commands, when required. This is called from various check points in the main +// program, primarily where there may be a while loop waiting for a buffer to clear space or any +// point where the execution time from the last check point may be more than a fraction of a second. +// This is a way to execute runtime commands asynchronously (aka multitasking) with grbl's g-code +// parsing and planning functions. This function also serves as an interface for the interrupts to +// set the system runtime flags, where only the main program handles them, removing the need to +// define more computationally-expensive volatile variables. This also provides a controlled way to +// execute certain tasks without having two or more instances of the same task, such as the planner +// recalculating the buffer upon a feedhold or override. +// NOTE: The sys.execute variable flags are set by any process, step or serial interrupts, pinouts, +// limit switches, or the main program. +void protocol_execute_runtime() +{ + uint8_t rt_exec = sys.execute; // Copy to avoid calling volatile multiple times + if (rt_exec) { // Enter only if any bit flag is true + + // System alarm. Everything has shutdown by something that has gone severely wrong. Report + // the source of the error to the user. If critical, Grbl disables by entering an infinite + // loop until system reset/abort. + if (rt_exec & (EXEC_ALARM | EXEC_CRIT_EVENT)) { + sys.state = STATE_ALARM; // Set system alarm state + + // Critical event. Only hard/soft limit errors currently qualify. + if (rt_exec & EXEC_CRIT_EVENT) { + report_alarm_message(ALARM_LIMIT_ERROR); + report_feedback_message(MESSAGE_CRITICAL_EVENT); + bit_false(sys.execute,EXEC_RESET); // Disable any existing reset + do { + // Nothing. Block EVERYTHING until user issues reset or power cycles. Hard limits + // typically occur while unattended or not paying attention. Gives the user time + // to do what is needed before resetting, like killing the incoming stream. The + // same could be said about soft limits. While the position is not lost, the incoming + // stream could be still engaged and cause a serious crash if it continues afterwards. + } while (bit_isfalse(sys.execute,EXEC_RESET)); + + // Standard alarm event. Only abort during motion qualifies. + } else { + // Runtime abort command issued during a cycle, feed hold, or homing cycle. Message the + // user that position may have been lost and set alarm state to enable the alarm lockout + // to indicate the possible severity of the problem. + report_alarm_message(ALARM_ABORT_CYCLE); + } + bit_false(sys.execute,(EXEC_ALARM | EXEC_CRIT_EVENT)); + } + + // Execute system abort. + if (rt_exec & EXEC_RESET) { + sys.abort = true; // Only place this is set true. + return; // Nothing else to do but exit. + } + + // Execute and serial print status + if (rt_exec & EXEC_STATUS_REPORT) { + report_realtime_status(); + bit_false(sys.execute,EXEC_STATUS_REPORT); + } + + // Execute a feed hold with deceleration, only during cycle. + if (rt_exec & EXEC_FEED_HOLD) { + // !!! During a cycle, the segment buffer has just been reloaded and full. So the math involved + // with the feed hold should be fine for most, if not all, operational scenarios. + if (sys.state == STATE_CYCLE) { + sys.state = STATE_HOLD; + st_update_plan_block_parameters(); + st_prep_buffer(); + sys.auto_start = false; // Disable planner auto start upon feed hold. + } + bit_false(sys.execute,EXEC_FEED_HOLD); + } + + // Execute a cycle start by starting the stepper interrupt begin executing the blocks in queue. + if (rt_exec & EXEC_CYCLE_START) { + if (sys.state == STATE_QUEUED) { + sys.state = STATE_CYCLE; + st_prep_buffer(); // Initialize step segment buffer before beginning cycle. + st_wake_up(); + if (bit_istrue(settings.flags,BITFLAG_AUTO_START)) { + sys.auto_start = true; // Re-enable auto start after feed hold. + } else { + sys.auto_start = false; // Reset auto start per settings. + } + } + bit_false(sys.execute,EXEC_CYCLE_START); + } + + // 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. + // NOTE: EXEC_CYCLE_STOP is set by the stepper subsystem when a cycle or feed hold completes. + if (rt_exec & EXEC_CYCLE_STOP) { + if (sys.state != STATE_QUEUED) { + sys.state = STATE_IDLE; + } + bit_false(sys.execute,EXEC_CYCLE_STOP); + } + + } + + // Overrides flag byte (sys.override) and execution should be installed here, since they + // are runtime and require a direct and controlled interface to the main stepper program. + + // Reload step segment buffer + if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_HOMING)) { st_prep_buffer(); } + +} + + +// Block until all buffered steps are executed or in a cycle state. Works with feed hold +// during a synchronize call, if it should happen. Also, waits for clean cycle end. +void protocol_buffer_synchronize() +{ + // Check and set auto start to resume cycle after synchronize and caller completes. + if (sys.state == STATE_CYCLE) { sys.auto_start = true; } + while (plan_get_current_block() || (sys.state == STATE_CYCLE)) { + protocol_execute_runtime(); // Check and execute run-time commands + if (sys.abort) { return; } // Check for system abort + } +} + + +// Auto-cycle start has two purposes: 1. Resumes a plan_synchronize() call from a function that +// requires the planner buffer to empty (spindle enable, dwell, etc.) 2. As a user setting that +// automatically begins the cycle when a user enters a valid motion command manually. This is +// intended as a beginners feature to help new users to understand g-code. It can be disabled +// as a beginner tool, but (1.) still operates. If disabled, the operation of cycle start is +// manually issuing a cycle start command whenever the user is ready and there is a valid motion +// command in the planner queue. +// NOTE: This function is called from the main loop and mc_line() only and executes when one of +// two conditions exist respectively: There are no more blocks sent (i.e. streaming is finished, +// single commands), or the planner buffer is full and ready to go. +void protocol_auto_cycle_start() { if (sys.auto_start) { sys.execute |= EXEC_CYCLE_START; } } diff --git a/protocol.h b/protocol.h index 9107d1d..7bafba2 100644 --- a/protocol.h +++ b/protocol.h @@ -1,5 +1,5 @@ /* - protocol.h - controls Grbl execution procedures + protocol.h - controls Grbl execution protocol and procedures Part of Grbl Copyright (c) 2011-2014 Sungeun K. Jeon @@ -31,11 +31,26 @@ #define LINE_BUFFER_SIZE 70 #endif +// Starts Grbl main loop. It handles all incoming characters from the serial port and executes +// them as they complete. It is also responsible for finishing the initialization procedures. +void protocol_main_loop(); + // Checks and executes a runtime command at various stop points in main program void protocol_execute_runtime(); -// Starts Grbl main loop. It handles all incoming characters from the serial port and executes -// them as they complete. It is also responsible for finishing the initialization procedures. -void protocol_process(); +// Notify the stepper subsystem to start executing the g-code program in buffer. +// void protocol_cycle_start(); + +// Reinitializes the buffer after a feed hold for a resume. +// void protocol_cycle_reinitialize(); + +// Initiates a feed hold of the running program +// void protocol_feed_hold(); + +// Executes the auto cycle feature, if enabled. +void protocol_auto_cycle_start(); + +// Block until all buffered steps are executed +void protocol_buffer_synchronize(); #endif diff --git a/report.c b/report.c index ee4de61..5416651 100644 --- a/report.c +++ b/report.c @@ -32,6 +32,7 @@ #include "settings.h" #include "gcode.h" #include "coolant_control.h" +#include "spindle_control.h" // Handles the primary confirmation protocol response for streaming interfaces and human-feedback. @@ -260,9 +261,9 @@ void report_gcode_modes() } switch (gc.spindle_direction) { - case 1 : printPgmString(PSTR(" M3")); break; - case -1 : printPgmString(PSTR(" M4")); break; - case 0 : printPgmString(PSTR(" M5")); break; + case SPINDLE_ENABLE_CW : printPgmString(PSTR(" M3")); break; + case SPINDLE_ENABLE_CCW : printPgmString(PSTR(" M4")); break; + case SPINDLE_DISABLE : printPgmString(PSTR(" M5")); break; } switch (gc.coolant_mode) { diff --git a/spindle_control.c b/spindle_control.c index e97c3f7..ad3efc9 100644 --- a/spindle_control.c +++ b/spindle_control.c @@ -21,23 +21,21 @@ #include "system.h" #include "spindle_control.h" -#include "planner.h" +#include "protocol.h" void spindle_init() -{ - SPINDLE_DIRECTION_DDR |= (1<prescaler<cycles_per_tick; st.step_count = st.exec_segment->n_step; // NOTE: Can sometimes be zero when moving slow. // If the new segment starts a new planner block, initialize stepper variables and counters. @@ -310,17 +322,22 @@ ISR(TIMER1_COMPA_vect) if ( st.exec_block_index != st.exec_segment->st_block_index ) { st.exec_block_index = st.exec_segment->st_block_index; st.exec_block = &st_block_buffer[st.exec_block_index]; - st.dir_outbits = st.exec_block->direction_bits ^ settings.dir_invert_mask; + // Initialize Bresenham line and distance counters st.counter_x = (st.exec_block->step_event_count >> 1); st.counter_y = st.counter_x; st.counter_z = st.counter_x; } + + st.dir_outbits = st.exec_block->direction_bits ^ settings.dir_invert_mask; + #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + // With AMASS enabled, adjust Bresenham axis increment counters according to AMASS level. st.steps[X_AXIS] = st.exec_block->steps[X_AXIS] >> st.exec_segment->amass_level; st.steps[Y_AXIS] = st.exec_block->steps[Y_AXIS] >> st.exec_segment->amass_level; st.steps[Z_AXIS] = st.exec_block->steps[Z_AXIS] >> st.exec_segment->amass_level; #endif + } else { // Segment buffer empty. Shutdown. st_go_idle(); @@ -379,28 +396,27 @@ ISR(TIMER1_COMPA_vect) st.step_outbits ^= settings.step_invert_mask; // Apply step port invert mask busy = false; -// SPINDLE_ENABLE_PORT ^= 1<direction_bits = pl_block->direction_bits; #ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING @@ -565,6 +529,9 @@ void st_prep_buffer() st_prep_block->steps[Z_AXIS] = pl_block->steps[Z_AXIS]; st_prep_block->step_event_count = pl_block->step_event_count; #else + // With AMASS enabled, simply bit-shift multiply all Bresenham data by the max AMASS + // level, such that we never divide beyond the original data anywhere in the algorithm. + // If the original data is divided, we can lose a step from integer roundoff. st_prep_block->steps[X_AXIS] = pl_block->steps[X_AXIS] << MAX_AMASS_LEVEL; st_prep_block->steps[Y_AXIS] = pl_block->steps[Y_AXIS] << MAX_AMASS_LEVEL; st_prep_block->steps[Z_AXIS] = pl_block->steps[Z_AXIS] << MAX_AMASS_LEVEL; @@ -574,8 +541,9 @@ void st_prep_buffer() // Initialize segment buffer data for generating the segments. prep.steps_remaining = pl_block->step_event_count; prep.step_per_mm = prep.steps_remaining/pl_block->millimeters; - prep.mm_per_step = pl_block->millimeters/prep.steps_remaining; - prep.minimum_mm = pl_block->millimeters-prep.mm_per_step; + prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR*pl_block->millimeters/prep.steps_remaining; + + prep.dt_remainder = 0.0; // Reset for new planner block if (sys.state == STATE_HOLD) { // Override planner block entry speed and enforce deceleration during feed hold. @@ -593,18 +561,20 @@ void st_prep_buffer() */ prep.mm_complete = 0.0; // Default velocity profile complete at 0.0mm from end of block. float inv_2_accel = 0.5/pl_block->acceleration; - if (sys.state == STATE_HOLD) { + if (sys.state == STATE_HOLD) { // [Forced Deceleration to Zero Velocity] // Compute velocity profile parameters for a feed hold in-progress. This profile overrides // the planner block profile, enforcing a deceleration to zero speed. prep.ramp_type = RAMP_DECEL; - float decel_dist = inv_2_accel*pl_block->entry_speed_sqr; - if (decel_dist < pl_block->millimeters) { - prep.exit_speed = 0.0; - prep.mm_complete = pl_block->millimeters-decel_dist; // End of feed hold. - } else { + // Compute decelerate distance relative to end of block. + float decel_dist = pl_block->millimeters - inv_2_accel*pl_block->entry_speed_sqr; + if (decel_dist < 0.0) { + // Deceleration through entire planner block. End of feed hold is not in this block. prep.exit_speed = sqrt(pl_block->entry_speed_sqr-2*pl_block->acceleration*pl_block->millimeters); + } else { + prep.mm_complete = decel_dist; // End of feed hold. + prep.exit_speed = 0.0; } - } else { + } else { // [Normal Operation] // Compute or recompute velocity profile parameters of the prepped planner block. prep.ramp_type = RAMP_ACCEL; // Initialize as acceleration ramp. prep.accelerate_until = pl_block->millimeters; @@ -640,7 +610,8 @@ void st_prep_buffer() // prep.decelerate_after = 0.0; prep.maximum_speed = prep.exit_speed; } - } + } + } // Initialize new segment @@ -649,6 +620,24 @@ void st_prep_buffer() // Set new segment to point to the current segment data block. prep_segment->st_block_index = prep.st_block_index; + + float mm_remaining = pl_block->millimeters; + float minimum_mm = pl_block->millimeters-prep.req_mm_increment; + if (minimum_mm < 0.0) { minimum_mm = 0.0; } + if (sys.state == STATE_HOLD) { + if (minimum_mm < prep.mm_complete) { // NOTE: Exit condition + // Less than one step to decelerate to zero speed, but already very close. AMASS + // requires full steps to execute. So, just bail. + prep.current_speed = 0.0; + prep.dt_remainder = 0.0; + prep.steps_remaining = ceil(pl_block->millimeters * prep.step_per_mm); + pl_block->millimeters = prep.steps_remaining/prep.step_per_mm; // Update with full steps. + plan_cycle_reinitialize(); + sys.state = STATE_QUEUED; + return; // Segment not generated, but current step data still retained. + } + } + /*------------------------------------------------------------------------------------ Compute the average velocity of this new segment by determining the total distance traveled over the segment time DT_SEGMENT. The following code first attempts to create @@ -663,12 +652,11 @@ void st_prep_buffer() the end of planner block (typical) or mid-block at the end of a forced deceleration, such as from a feed hold. */ - float dt_max = DT_SEGMENT; // Set maximum segment time + float dt_max = DT_SEGMENT; // Maximum segment time float dt = 0.0; // Initialize segment time - float mm_remaining = pl_block->millimeters; float time_var = dt_max; // Time worker variable float mm_var; // mm-Distance worker variable - float speed_var; // Speed worker variable. + float speed_var; // Speed worker variable do { switch (prep.ramp_type) { case RAMP_ACCEL: @@ -716,7 +704,7 @@ void st_prep_buffer() dt += time_var; // Add computed ramp time to total segment time. if (dt < dt_max) { time_var = dt_max - dt; } // **Incomplete** At ramp junction. else { - if (mm_remaining > prep.minimum_mm) { // Check for slow segments with zero steps. + if (mm_remaining > minimum_mm) { // Check for very slow segments with zero steps. dt_max += DT_SEGMENT; // Increase segment time to ensure at least one step in segment. time_var = dt_max - dt; } else { @@ -724,9 +712,10 @@ void st_prep_buffer() } } } while (mm_remaining > prep.mm_complete); // **Complete** Exit loop. Profile complete. + /* ----------------------------------------------------------------------------------- - Compute segment step rate, steps to execute, and step phase correction parameters. + Compute segment step rate, steps to execute, and apply necessary rate corrections. NOTE: Steps are computed by direct scalar conversion of the millimeter distance remaining in the block, rather than incrementally tallying the steps executed per segment. This helps in removing floating point round-off issues of several additions. @@ -735,15 +724,26 @@ void st_prep_buffer() Fortunately, this scenario is highly unlikely and unrealistic in CNC machines supported by Grbl (i.e. exceeding 10 meters axis travel at 200 step/mm). */ - uint32_t cycles; - float steps_remaining = prep.step_per_mm*mm_remaining; + float steps_remaining = prep.step_per_mm*mm_remaining; // Convert mm_remaining to steps + float n_steps_remaining = ceil(steps_remaining); // Round-up current steps remaining + float last_n_steps_remaining = ceil(prep.steps_remaining); // Round-up last steps remaining + prep_segment->n_step = last_n_steps_remaining-n_steps_remaining; // Compute number of steps to execute. - // Compute number of steps to execute and segment step phase correction. - prep_segment->n_step = ceil(prep.steps_remaining)-ceil(steps_remaining); + // Compute segment step rate. Since steps are integers and mm distances traveled are not, + // the end of every segment can have a partial step of varying magnitudes that are not + // executed, because the stepper ISR requires whole steps due to the AMASS algorithm. To + // compensate, we track the time to execute the previous segment's partial step and simply + // apply it with the partial step distance to the current segment, so that it minutely + // adjusts the whole segment rate to keep step output exact. These rate adjustments are + // typically very small and do not adversely effect performance, but ensures that Grbl + // outputs the exact acceleration and velocity profiles as computed by the planner. + dt += prep.dt_remainder; // Apply previous segment partial step execute time + float inv_rate = dt/(last_n_steps_remaining - steps_remaining); // Compute adjusted step rate inverse + prep.dt_remainder = (n_steps_remaining - steps_remaining)*inv_rate; // Update segment partial step time + + // Compute CPU cycles per step for the prepped segment. + uint32_t cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step) - float inv_rate = dt/(prep.steps_remaining-steps_remaining); - cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step) - #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING // Compute step timing and multi-axis smoothing level. // NOTE: AMASS overdrives the timer with each level, so only one prescalar is required. @@ -777,32 +777,27 @@ void st_prep_buffer() // Determine end of segment conditions. Setup initial conditions for next segment. if (mm_remaining > prep.mm_complete) { - // Normal operation. Block incomplete. Distance remaining to be executed. - prep.minimum_mm = prep.mm_per_step*floor(steps_remaining); + // Normal operation. Block incomplete. Distance remaining in block to be executed. pl_block->millimeters = mm_remaining; prep.steps_remaining = steps_remaining; } else { // End of planner block or forced-termination. No more distance to be executed. if (mm_remaining > 0.0) { // At end of forced-termination. - // NOTE: Currently only feed holds qualify for this scenario. May change with overrides. + // Reset prep parameters for resuming and then bail. + // NOTE: Currently only feed holds qualify for this scenario. May change with overrides. prep.current_speed = 0.0; + prep.dt_remainder = 0.0; prep.steps_remaining = ceil(steps_remaining); - prep.minimum_mm = prep.steps_remaining-prep.mm_per_step; - pl_block->millimeters = prep.steps_remaining*prep.mm_per_step; // Update with full steps. - plan_cycle_reinitialize(); - sys.state = STATE_QUEUED; // End cycle. + pl_block->millimeters = prep.steps_remaining/prep.step_per_mm; // Update with full steps. + plan_cycle_reinitialize(); + sys.state = STATE_QUEUED; // End cycle. + +// TODO: Try to move QUEUED setting into cycle re-initialize. + } else { // End of planner block // The planner block is complete. All steps are set to be executed in the segment buffer. pl_block = NULL; plan_discard_current_block(); - - if (sys.state == STATE_HOLD) { - if (prep.current_speed == 0.0) { -// TODO: Check if the segment buffer gets initialized correctly. - plan_cycle_reinitialize(); - sys.state = STATE_QUEUED; - } - } } } @@ -810,6 +805,8 @@ void st_prep_buffer() segment_buffer_head = segment_next_head; if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { segment_next_head = 0; } + if (sys.state == STATE_QUEUED) { return; } // Bail if hold completes + // int32_t blength = segment_buffer_head - segment_buffer_tail; // if (blength < 0) { blength += SEGMENT_BUFFER_SIZE; } // printInteger(blength); diff --git a/stepper.h b/stepper.h index fe9323f..1cc06d9 100644 --- a/stepper.h +++ b/stepper.h @@ -38,15 +38,6 @@ void st_go_idle(); // Reset the stepper subsystem variables void st_reset(); -// Notify the stepper subsystem to start executing the g-code program in buffer. -void st_cycle_start(); - -// Reinitializes the buffer after a feed hold for a resume. -void st_cycle_reinitialize(); - -// Initiates a feed hold of the running program -void st_feed_hold(); - // Reloads step segment buffer. Called continuously by runtime execution system. void st_prep_buffer(); diff --git a/system.c b/system.c index 4dfe31d..229203f 100644 --- a/system.c +++ b/system.c @@ -28,7 +28,7 @@ void system_init() { - PINOUT_DDR &= ~(PINOUT_MASK); // Set as input pins + PINOUT_DDR &= ~(PINOUT_MASK); // Configure as input pins PINOUT_PORT |= PINOUT_MASK; // Enable internal pull-up resistors. Normal high operation. PINOUT_PCMSK |= PINOUT_MASK; // Enable specific pins of the Pin Change Interrupt PCICR |= (1 << PINOUT_INT); // Enable Pin Change Interrupt @@ -85,6 +85,7 @@ uint8_t system_execute_line(char *line) uint8_t helper_var = 0; // Helper variable float parameter, value; switch( line[char_counter] ) { + case 0 : report_grbl_help(); break; case '#' : // Print gcode parameters if ( line[++char_counter] != 0 ) { return(STATUS_UNSUPPORTED_STATEMENT); } else { report_gcode_parameters(); } @@ -93,7 +94,29 @@ uint8_t system_execute_line(char *line) if ( line[++char_counter] != 0 ) { return(STATUS_UNSUPPORTED_STATEMENT); } else { report_gcode_modes(); } break; -// case 'J' : break; // Jogging methods + case 'C' : // Set check g-code mode [IDLE/CHECK] + if ( line[++char_counter] != 0 ) { return(STATUS_UNSUPPORTED_STATEMENT); } + // Perform reset when toggling off. Check g-code mode should only work if Grbl + // is idle and ready, regardless of alarm locks. This is mainly to keep things + // simple and consistent. + if ( sys.state == STATE_CHECK_MODE ) { + mc_reset(); + report_feedback_message(MESSAGE_DISABLED); + } else { + if (sys.state) { return(STATUS_IDLE_ERROR); } // Requires no alarm mode. + sys.state = STATE_CHECK_MODE; + report_feedback_message(MESSAGE_ENABLED); + } + break; + case 'X' : // Disable alarm lock [ALARM] + if ( line[++char_counter] != 0 ) { return(STATUS_UNSUPPORTED_STATEMENT); } + if (sys.state == STATE_ALARM) { + report_feedback_message(MESSAGE_ALARM_UNLOCK); + sys.state = STATE_IDLE; + // Don't run startup script. Prevents stored moves in startup from causing accidents. + } // Otherwise, no effect. + break; +// case 'J' : break; // Jogging methods // TODO: Here jogging can be placed for execution as a seperate subprogram. It does not need to be // susceptible to other runtime commands except for e-stop. The jogging function is intended to // be a basic toggle on/off with controlled acceleration and deceleration to prevent skipped @@ -109,41 +132,18 @@ uint8_t system_execute_line(char *line) // Block any system command that requires the state as IDLE/ALARM. (i.e. EEPROM, homing) if ( !(sys.state == STATE_IDLE || sys.state == STATE_ALARM) ) { return(STATUS_IDLE_ERROR); } switch( line[char_counter] ) { - case 0 : report_grbl_help(); break; - case '$' : // Prints Grbl settings + case '$' : // Prints Grbl settings [IDLE/ALARM] if ( line[++char_counter] != 0 ) { return(STATUS_UNSUPPORTED_STATEMENT); } else { report_grbl_settings(); } break; - case 'C' : // Set check g-code mode - if ( line[++char_counter] != 0 ) { return(STATUS_UNSUPPORTED_STATEMENT); } - // Perform reset when toggling off. Check g-code mode should only work if Grbl - // is idle and ready, regardless of alarm locks. This is mainly to keep things - // simple and consistent. - if ( sys.state == STATE_CHECK_MODE ) { - mc_reset(); - report_feedback_message(MESSAGE_DISABLED); - } else { - if (sys.state) { return(STATUS_IDLE_ERROR); } - sys.state = STATE_CHECK_MODE; - report_feedback_message(MESSAGE_ENABLED); - } - break; - case 'X' : // Disable alarm lock - if ( line[++char_counter] != 0 ) { return(STATUS_UNSUPPORTED_STATEMENT); } - if (sys.state == STATE_ALARM) { - report_feedback_message(MESSAGE_ALARM_UNLOCK); - sys.state = STATE_IDLE; - // Don't run startup script. Prevents stored moves in startup from causing accidents. - } - break; - case 'H' : // Perform homing cycle + case 'H' : // Perform homing cycle [IDLE/ALARM] if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { // Only perform homing if Grbl is idle or lost. mc_homing_cycle(); if (!sys.abort) { system_execute_startup(line); } // Execute startup scripts after successful homing. } else { return(STATUS_SETTING_DISABLED); } break; - case 'I' : // Print or store build info. + case 'I' : // Print or store build info. [IDLE/ALARM] if ( line[++char_counter] == 0 ) { if (!(settings_read_build_info(line))) { report_status_message(STATUS_SETTING_READ_FAIL); @@ -159,7 +159,7 @@ uint8_t system_execute_line(char *line) settings_store_build_info(line); } break; - case 'N' : // Startup lines. + case 'N' : // Startup lines. [IDLE/ALARM] if ( line[++char_counter] == 0 ) { // Print startup lines for (helper_var=0; helper_var < N_STARTUP_LINE; helper_var++) { if (!(settings_read_startup_line(helper_var, line))) { @@ -170,10 +170,11 @@ uint8_t system_execute_line(char *line) } break; } else { // Store startup line + if (sys.state != STATE_IDLE) { return(STATUS_IDLE_ERROR); } // Store only when idle. helper_var = true; // Set helper_var to flag storing method. // No break. Continues into default: to read remaining command characters. } - default : // Storing setting methods + default : // Storing setting methods [IDLE/ALARM] if(!read_float(line, &char_counter, ¶meter)) { return(STATUS_BAD_NUMBER_FORMAT); } if(line[char_counter++] != '=') { return(STATUS_UNSUPPORTED_STATEMENT); } if (helper_var) { // Store startup line