Refactoring and lots of bug fixes. Updated homing cycle.

WARNING: There are still some bugs to be worked out. Please use caution
if you test this firmware.

- Feed holds work much better, but there are still some failure
conditions that need to be worked out. This is the being worked on
currently and a fix is planned to be pushed next.

- Homing cycle refactoring: Slight adjustment of the homing cycle to
allow for limit pins to be shared by different axes, as long as the
shared limit pins are not homed on the same cycle. Also, removed the
LOCATE_CYCLE portion of the homing cycle configuration. It was
redundant.

- Limit pin sharing: (See above). To clear up one or two limit pins for
other IO, limit pins can now be shared. For example, the Z-limit can be
shared with either X or Y limit pins, because it’s on a separate homing
cycle. Hard limit will still work exactly as before.

- Spindle pin output fixed. The pins weren’t getting initialized
correctly.

- Fixed a cycle issue where streaming was working almost like a single
block mode. This was caused by a problem with the spindle_run() and
coolant_run() commands and issuing an unintended planner buffer sync.

- Refactored the cycle_start, feed_hold, and other runtime routines
into the runtime command module, where they should be handled here
only. These were redundant.

- Moved some function calls around into more appropriate source code
modules.

- Fixed the reporting of spindle state.
This commit is contained in:
Sonny Jeon 2014-02-09 10:46:34 -07:00
parent cc9afdc195
commit 50fbc6e297
20 changed files with 579 additions and 529 deletions

View File

@ -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_

View File

@ -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<<Z_AXIS) // First move Z to clear workspace.
#define HOMING_SEARCH_CYCLE_1 ((1<<X_AXIS)|(1<<Y_AXIS)) // Then move X,Y at the same time.
// #define HOMING_SEARCH_CYCLE_2 // Uncomment and add axes mask to enable
#define HOMING_LOCATE_CYCLE ((1<<X_AXIS)|(1<<Y_AXIS)|(1<<Z_AXIS)) // Must contain ALL search axes
// Define the homing cycle patterns with bitmasks. The homing cycle first performs a search mode
// to quickly engage the limit switches, followed by a slower locate mode, and finished by a short
// pull-off motion to disengage the limit switches. The following HOMING_CYCLE_x defines are executed
// in order starting with suffix 0 and completes the homing routine for the specified-axes only. If
// an axis is omitted from the defines, it will not home, nor will the system update its position.
// Meaning that this allows for users with non-standard cartesian machines, such as a lathe (x then z,
// with no y), to configure the homing cycle behavior to their needs.
// NOTE: The homing cycle is designed to allow sharing of limit pins, if the axes are not in the same
// cycle, but this requires some pin settings changes in cpu_map.h file. For example, the default homing
// cycle can share the Z limit pin with either X or Y limit pins, since they are on different cycles.
// By sharing a pin, this frees up a precious IO pin for other purposes. In theory, all axes limit pins
// may be reduced to one pin, if all axes are homed with seperate cycles, or vice versa, all three axes
// on separate pin, but homed in one cycle. Also, it should be noted that the function of hard limits
// will not be affected by pin sharing.
// NOTE: Defaults are set for a traditional 3-axis CNC machine. Z-axis first to clear, followed by X & Y.
#define HOMING_CYCLE_0 (1<<Z_AXIS) // REQUIRED: First move Z to clear workspace.
#define HOMING_CYCLE_1 ((1<<X_AXIS)|(1<<Y_AXIS)) // OPTIONAL: Then move X,Y at the same time.
// #define HOMING_CYCLE_2 // OPTIONAL: Uncomment and add axes mask to enable
// Number of homing cycles performed after when the machine initially jogs to limit switches.
// This help in preventing overshoot and should improve repeatability. This value should be one or
@ -80,7 +85,7 @@
#define N_STARTUP_LINE 2 // Integer (1-3)
// Enables a second coolant control pin via the mist coolant g-code command M7 on the Arduino Uno
// analog pin 5. Only use this option if you require a second control pin.
// analog pin 5. Only use this option if you require a second coolant control pin.
// NOTE: The M8 flood coolant control pin on analog pin 4 will still be functional regardless.
// #define ENABLE_M7 // Mist coolant disabled by default. See config.h to enable/disable.
@ -130,6 +135,7 @@
// generations. In general, the default value is more than enough for the intended CNC applications
// of grbl, and should be on the order or greater than the size of the buffer to help with the
// computational efficiency of generating arcs.
// NOTE: Arcs are now generated by a chordal tolerance
#define N_ARC_CORRECTION 20 // Integer (1-255)
// Time delay increments performed during a dwell. The default value is set at 50ms, which provides

View File

@ -20,7 +20,7 @@
#include "system.h"
#include "coolant_control.h"
#include "planner.h"
#include "protocol.h"
void coolant_init()
@ -44,7 +44,7 @@ void coolant_stop()
void coolant_run(uint8_t mode)
{
plan_synchronize(); // Ensure coolant turns on when specified in program.
protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program.
if (mode == COOLANT_FLOOD_ENABLE) {
COOLANT_FLOOD_PORT |= (1 << COOLANT_FLOOD_BIT);

View File

@ -37,8 +37,8 @@
#define SERIAL_UDRE USART_UDRE_vect
// Define step pulse output pins. NOTE: All step bit pins must be on the same port.
#define STEPPING_DDR DDRD
#define STEPPING_PORT PORTD
#define STEP_DDR DDRD
#define STEP_PORT PORTD
#define X_STEP_BIT 2 // Uno Digital Pin 2
#define Y_STEP_BIT 3 // Uno Digital Pin 3
#define Z_STEP_BIT 4 // Uno Digital Pin 4
@ -147,9 +147,9 @@
//#define LINE_BUFFER_SIZE 100
// Define step pulse output pins. NOTE: All step bit pins must be on the same port.
#define STEPPING_DDR DDRA
#define STEPPING_PORT PORTA
#define STEPPING_PIN PINA
#define STEP_DDR DDRA
#define STEP_PORT PORTA
#define STEP_PIN PINA
#define X_STEP_BIT 2 // MEGA2560 Digital Pin 24
#define Y_STEP_BIT 3 // MEGA2560 Digital Pin 25
#define Z_STEP_BIT 4 // MEGA2560 Digital Pin 26

23
gcode.c
View File

@ -24,8 +24,8 @@
#include "system.h"
#include "settings.h"
#include "protocol.h"
#include "gcode.h"
#include "planner.h"
#include "motion_control.h"
#include "spindle_control.h"
#include "coolant_control.h"
@ -86,11 +86,6 @@ static float to_millimeters(float value)
// coordinates, respectively.
uint8_t gc_execute_line(char *line)
{
// If in alarm state, don't process. Immediately return with error.
// NOTE: Might not be right place for this, but also prevents $N storing during alarm.
if (sys.state == STATE_ALARM) { return(STATUS_ALARM_LOCK); }
uint8_t char_counter = 0;
char letter;
float value;
@ -262,10 +257,14 @@ uint8_t gc_execute_line(char *line)
// ([M6]: Tool change should be executed here.)
// [M3,M4,M5]: Update spindle state
spindle_run(gc.spindle_direction, gc.spindle_speed);
if (bit_istrue(modal_group_words,bit(MODAL_GROUP_7))) {
spindle_run(gc.spindle_direction, gc.spindle_speed);
}
// [*M7,M8,M9]: Update coolant state
coolant_run(gc.coolant_mode);
if (bit_istrue(modal_group_words,bit(MODAL_GROUP_8))) {
coolant_run(gc.coolant_mode);
}
}
// [G54,G55,...,G59]: Coordinate system selection
@ -458,15 +457,15 @@ uint8_t gc_execute_line(char *line)
// M0,M1,M2,M30: Perform non-running program flow actions. During a program pause, the buffer may
// refill and can only be resumed by the cycle start run-time command.
if (gc.program_flow) {
plan_synchronize(); // Finish all remaining buffered motions. Program paused when complete.
if (gc.program_flow) {
protocol_buffer_synchronize(); // Finish all remaining buffered motions. Program paused when complete.
sys.auto_start = false; // Disable auto cycle start. Forces pause until cycle start issued.
// If complete, reset to reload defaults (G92.2,G54,G17,G90,G94,M48,G40,M5,M9). Otherwise,
// re-enable program flow after pause complete, where cycle start will resume the program.
if (gc.program_flow == PROGRAM_FLOW_COMPLETED) { mc_reset(); }
else { gc.program_flow = PROGRAM_FLOW_RUNNING; }
}
}
return(gc.status_code);
}

View File

@ -71,7 +71,7 @@ typedef struct {
uint8_t absolute_mode; // 0 = relative motion, 1 = absolute motion {G90, G91}
uint8_t program_flow; // {M0, M1, M2, M30}
uint8_t coolant_mode; // 0 = Disable, 1 = Flood Enable, 2 = Mist Enable {M8, M9}
int8_t spindle_direction; // 1 = CW, 2 = CCW, 0 = Stop {M3, M4, M5}
uint8_t spindle_direction; // 1 = CW, 2 = CCW, 0 = Stop {M3, M4, M5}
float spindle_speed; // RPM
float feed_rate; // Millimeters/min
float position[N_AXIS]; // Where the interpreter considers the tool to be at this point in the code

228
limits.c
View File

@ -29,6 +29,9 @@
#include "report.h"
#define HOMING_AXIS_SEARCH_SCALAR 1.5 // Axis search distance multiplier. Must be > 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<<WDIE))) { WDTCSR |= (1<<WDIE); } }
ISR(WDT_vect)
#ifndef ENABLE_SOFTWARE_DEBOUNCE
ISR(LIMIT_INT_vect) // DEFAULT: Limit pin change interrupt process.
{
WDTCSR &= ~(1<<WDIE);
// Ignore limit switches if already in an alarm state or in-process of executing an alarm.
// When in the alarm state, Grbl should have been reset or will force a reset, so any pending
// moves in the planner and serial buffers are all cleared and newly sent blocks will be
// locked out until a homing cycle or a kill lock command. Allows the user to disable the hard
// limit setting if their limits are constantly triggering after a reset and move their axes.
if (sys.state != STATE_ALARM) {
if (sys.state != STATE_ALARM) {
if (bit_isfalse(sys.execute,EXEC_ALARM)) {
mc_reset(); // Initiate system kill.
sys.execute |= EXEC_CRIT_EVENT; // Indicate hard limit critical event
}
}
}
#else // OPTIONAL: Software debounce limit pin routine.
// Upon limit pin change, enable watchdog timer to create a short delay.
ISR(LIMIT_INT_vect) { if (!(WDTCSR & (1<<WDIE))) { WDTCSR |= (1<<WDIE); } }
ISR(WDT_vect) // Watchdog timer ISR
{
WDTCSR &= ~(1<<WDIE); // Disable watchdog timer.
if (sys.state != STATE_ALARM) { // Ignore if already in alarm state.
if (bit_isfalse(sys.execute,EXEC_ALARM)) {
uint8_t bits = LIMIT_PIN;
// Check limit pin state.
if (bit_istrue(settings.flags,BITFLAG_INVERT_LIMIT_PINS)) { bits ^= LIMIT_MASK; }
if (bits & LIMIT_MASK) {
mc_reset(); // Initiate system kill.
@ -91,101 +108,140 @@ void limits_disable()
}
}
}
#else
ISR(LIMIT_INT_vect)
{
// Ignore limit switches if already in an alarm state or in-process of executing an alarm.
// When in the alarm state, Grbl should have been reset or will force a reset, so any pending
// moves in the planner and serial buffers are all cleared and newly sent blocks will be
// locked out until a homing cycle or a kill lock command. Allows the user to disable the hard
// limit setting if their limits are constantly triggering after a reset and move their axes.
if (sys.state != STATE_ALARM) {
if (bit_isfalse(sys.execute,EXEC_ALARM)) {
mc_reset(); // Initiate system kill.
sys.execute |= EXEC_CRIT_EVENT; // Indicate hard limit critical event
}
}
}
#endif
// Moves specified cycle axes all at homing rate, either approaching or disengaging the limit
// switches. Homing is a special motion case, where there is only an acceleration followed
// by abrupt asynchronous stops by each axes reaching their limit switch independently. The
// asynchronous stops are handled by a system level axis lock mask, which prevents the stepper
// algorithm from executing step pulses.
// Homes the specified cycle axes, sets the machine position, and performs a pull-off motion after
// completing. Homing is a special motion case, which involves rapid uncontrolled stops to locate
// the trigger point of the limit switches. The rapid stops are handled by a system level axis lock
// mask, which prevents the stepper algorithm from executing step pulses. Homing motions typically
// circumvent the processes for executing motions in normal operation.
// NOTE: Only the abort runtime command can interrupt this process.
void limits_go_home(uint8_t cycle_mask, bool approach, float homing_rate)
void limits_go_home(uint8_t cycle_mask)
{
if (sys.execute & EXEC_RESET) { return; }
uint8_t invert_pin;
if (bit_isfalse(settings.flags,BITFLAG_INVERT_LIMIT_PINS)) { invert_pin = approach; }
else { invert_pin = !approach; }
if (sys.abort) { return; } // Block if system reset has been issued.
// Initialize homing in search mode to quickly engage the specified cycle_mask limit switches.
bool approach = true;
float homing_rate = settings.homing_seek_rate;
uint8_t invert_pin, idx;
uint8_t n_cycle = (2*N_HOMING_LOCATE_CYCLE+1);
float target[N_AXIS];
// Determine travel distance to the furthest homing switch based on user max travel settings.
// NOTE: settings.max_travel[] is stored as a negative value.
float max_travel = settings.max_travel[X_AXIS];
if (max_travel > 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<N_AXIS; i++) {
if (bit_istrue(cycle_mask,bit(i))) {
n_active_axis++;
target[i] = max_travel;
} else {
target[i] = 0.0;
}
}
if (bit_istrue(settings.homing_dir_mask,(1<<X_DIRECTION_BIT))) { target[X_AXIS] = -target[X_AXIS]; }
if (bit_istrue(settings.homing_dir_mask,(1<<Y_DIRECTION_BIT))) { target[Y_AXIS] = -target[Y_AXIS]; }
if (bit_istrue(settings.homing_dir_mask,(1<<Z_DIRECTION_BIT))) { target[Z_AXIS] = -target[Z_AXIS]; }
homing_rate *= sqrt(n_active_axis); // [sqrt(N_AXIS)] Adjust so individual axes all move at homing rate.
// Setup homing axis locks based on cycle mask.
uint8_t axislock = 0;
if (bit_istrue(cycle_mask,bit(X_AXIS))) { axislock |= (1<<X_STEP_BIT); }
if (bit_istrue(cycle_mask,bit(Y_AXIS))) { axislock |= (1<<Y_STEP_BIT); }
if (bit_istrue(cycle_mask,bit(Z_AXIS))) { axislock |= (1<<Z_STEP_BIT); }
sys.homing_axis_lock = axislock;
max_travel *= -HOMING_AXIS_SEARCH_SCALAR; // Ensure homing switches engaged by over-estimating max travel.
plan_reset(); // Reset planner buffer to zero planner current position and to clear previous motions.
// Perform homing cycle. Planner buffer should be empty, as required to initiate the homing cycle.
uint8_t limit_state;
plan_buffer_line(target, homing_rate, false); // Bypass mc_line(). Directly plan homing motion.
st_prep_buffer(); // Prep first segment from newly planned block.
st_wake_up(); // Initiate motion
do {
// Check limit state. Lock out cycle axes when they change.
limit_state = LIMIT_PIN;
if (invert_pin) { limit_state ^= LIMIT_MASK; }
if (axislock & (1<<X_STEP_BIT)) {
if (limit_state & (1<<X_LIMIT_BIT)) { axislock &= ~(1<<X_STEP_BIT); }
}
if (axislock & (1<<Y_STEP_BIT)) {
if (limit_state & (1<<Y_LIMIT_BIT)) { axislock &= ~(1<<Y_STEP_BIT); }
}
if (axislock & (1<<Z_STEP_BIT)) {
if (limit_state & (1<<Z_LIMIT_BIT)) { axislock &= ~(1<<Z_STEP_BIT); }
}
// Initialize invert_pin boolean based on approach and invert pin user setting.
if (bit_isfalse(settings.flags,BITFLAG_INVERT_LIMIT_PINS)) { invert_pin = approach; }
else { invert_pin = !approach; }
// Set target location and rate for active axes.
uint8_t n_active_axis = 0;
for (idx=0; idx<N_AXIS; idx++) {
if (bit_istrue(cycle_mask,bit(idx))) {
n_active_axis++;
if (!approach) { target[idx] = -max_travel; }
else { target[idx] = max_travel; }
} else {
target[idx] = 0.0;
}
}
if (bit_istrue(settings.homing_dir_mask,(1<<X_DIRECTION_BIT))) { target[X_AXIS] = -target[X_AXIS]; }
if (bit_istrue(settings.homing_dir_mask,(1<<Y_DIRECTION_BIT))) { target[Y_AXIS] = -target[Y_AXIS]; }
if (bit_istrue(settings.homing_dir_mask,(1<<Z_DIRECTION_BIT))) { target[Z_AXIS] = -target[Z_AXIS]; }
homing_rate *= sqrt(n_active_axis); // [sqrt(N_AXIS)] Adjust so individual axes all move at homing rate.
// Reset homing axis locks based on cycle mask.
uint8_t axislock = 0;
if (bit_istrue(cycle_mask,bit(X_AXIS))) { axislock |= (1<<X_STEP_BIT); }
if (bit_istrue(cycle_mask,bit(Y_AXIS))) { axislock |= (1<<Y_STEP_BIT); }
if (bit_istrue(cycle_mask,bit(Z_AXIS))) { axislock |= (1<<Z_STEP_BIT); }
sys.homing_axis_lock = axislock;
st_prep_buffer(); // Check and prep one segment. NOTE: Should take no longer than 200us.
if (sys.execute & EXEC_RESET) { return; }
} while (STEP_MASK & axislock);
st_go_idle(); // Disable steppers. Axes motion should already be locked.
plan_reset(); // Reset planner buffer. Ensure homing motion is cleared.
st_reset(); // Reset step segment buffer. Ensure homing motion is cleared.
delay_ms(settings.homing_debounce_delay); // Delay to allow transient dynamics to dissipate.
// Perform homing cycle. Planner buffer should be empty, as required to initiate the homing cycle.
uint8_t limit_state;
plan_buffer_line(target, homing_rate, false); // Bypass mc_line(). Directly plan homing motion.
st_prep_buffer(); // Prep and fill segment buffer from newly planned block.
st_wake_up(); // Initiate motion
do {
// Check limit state. Lock out cycle axes when they change.
limit_state = LIMIT_PIN;
if (invert_pin) { limit_state ^= LIMIT_MASK; }
if (axislock & (1<<X_STEP_BIT)) {
if (limit_state & (1<<X_LIMIT_BIT)) { axislock &= ~(1<<X_STEP_BIT); }
}
if (axislock & (1<<Y_STEP_BIT)) {
if (limit_state & (1<<Y_LIMIT_BIT)) { axislock &= ~(1<<Y_STEP_BIT); }
}
if (axislock & (1<<Z_STEP_BIT)) {
if (limit_state & (1<<Z_LIMIT_BIT)) { axislock &= ~(1<<Z_STEP_BIT); }
}
sys.homing_axis_lock = axislock;
st_prep_buffer(); // Check and prep segment buffer. NOTE: Should take no longer than 200us.
// Check only for user reset. No time to run protocol_execute_runtime() in this loop.
if (sys.execute & EXEC_RESET) { protocol_execute_runtime(); return; }
} while (STEP_MASK & axislock);
delay_ms(settings.homing_debounce_delay); // Delay to allow transient dynamics to dissipate.
// Reverse direction and reset homing rate for locate cycle(s).
homing_rate = settings.homing_feed_rate;
approach = !approach;
st_reset(); // Force disable steppers and reset step segment buffer. Ensure homing motion is cleared.
plan_reset(); // Reset planner buffer. Zero planner positions. Ensure homing motion is cleared.
} while (n_cycle-- > 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<N_AXIS; idx++) {
// Set up pull off targets and machine positions for limit switches homed in the negative
// direction, rather than the traditional positive. Leave non-homed positions as zero and
// do not move them.
// NOTE: settings.max_travel[] is stored as a negative value.
if (cycle_mask & bit(idx)) {
if ( settings.homing_dir_mask & get_direction_mask(idx) ) {
target[idx] = settings.homing_pulloff+settings.max_travel[idx];
sys.position[idx] = lround(settings.max_travel[idx]*settings.steps_per_mm[idx]);
} else {
target[idx] = -settings.homing_pulloff;
sys.position[idx] = 0;
}
} else { // Non-active cycle axis. Set target to not move during pull-off.
target[idx] = (float)sys.position[idx]/settings.steps_per_mm[idx];
}
}
plan_sync_position(); // Sync planner position to current machine position for pull-off move.
plan_buffer_line(target, settings.homing_seek_rate, false); // Bypass mc_line(). Directly plan motion.
// Initiate pull-off using main motion control routines.
// TODO : Clean up state routines so that this motion still shows homing state.
sys.state = STATE_QUEUED;
// protocol_cycle_start();
sys.execute |= EXEC_CYCLE_START;
protocol_execute_runtime();
protocol_buffer_synchronize(); // Complete pull-off motion.
// Set system state to homing before returning.
sys.state = STATE_HOMING;
}
// Performs a soft limit check. Called from mc_line() only. Assumes the machine has been homed,
// and the workspace volume is in all negative space.
// the workspace volume is in all negative space, and the system is in normal operation.
void limits_soft_check(float *target)
{
uint8_t idx;
@ -196,11 +252,11 @@ void limits_soft_check(float *target)
// workspace volume so just come to a controlled stop so position is not lost. When complete
// enter alarm mode.
if (sys.state == STATE_CYCLE) {
st_feed_hold();
while (sys.state == STATE_HOLD) {
sys.execute |= EXEC_FEED_HOLD;
do {
protocol_execute_runtime();
if (sys.abort) { return; }
}
} while (sys.state == STATE_HOLD);
}
mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown.

View File

@ -29,7 +29,7 @@ void limits_init();
void limits_disable();
// Perform one portion of the homing cycle based on the input settings.
void limits_go_home(uint8_t cycle_mask, bool approach, float homing_rate);
void limits_go_home(uint8_t cycle_mask);
// Check for soft limit violations
void limits_soft_check(float *target);

14
main.c
View File

@ -44,10 +44,10 @@ int main(void)
settings_init(); // Load grbl settings from EEPROM
stepper_init(); // Configure stepper pins and interrupt timers
system_init(); // Configure pinout pins and pin-change interrupt
sei();
memset(&sys, 0, sizeof(sys)); // Clear all system variables
sys.abort = true; // Set abort to complete initialization
sei(); // Enable interrupts
// Check for power-up and set system alarm if homing is enabled to force homing cycle
// by setting Grbl's alarm state. Alarm locks out all g-code commands, including the
@ -60,9 +60,14 @@ int main(void)
if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { sys.state = STATE_ALARM; }
#endif
// Grbl initialization loop upon power-up or a system abort. For the latter, all processes
// will return to this loop to be cleanly re-initialized.
for(;;) {
// TODO: Separate configure task that require interrupts to be disabled, especially upon
// a system abort and ensuring any active interrupts are cleanly reset.
// Reset the system primary functionality.
// Reset Grbl primary systems.
serial_reset_read_buffer(); // Clear serial read buffer
gc_init(); // Set g-code parser to default state
spindle_init();
@ -81,9 +86,8 @@ int main(void)
if (bit_istrue(settings.flags,BITFLAG_AUTO_START)) { sys.auto_start = true; }
else { sys.auto_start = false; }
// Start main loop. Processes inputs and executes them.
// NOTE: Upon a system abort, the main loop returns and re-initializes the system.
protocol_process();
// Start Grbl main loop. Processes program inputs and executes them.
protocol_main_loop();
}
return 0; /* Never reached */

View File

@ -64,7 +64,7 @@ void mc_line(float *target, float feed_rate, uint8_t invert_feed_rate)
do {
protocol_execute_runtime(); // Check for any run-time commands
if (sys.abort) { return; } // Bail, if system abort.
if ( plan_check_full_buffer() ) { mc_auto_cycle_start(); } // Auto-cycle start when buffer is full.
if ( plan_check_full_buffer() ) { protocol_auto_cycle_start(); } // Auto-cycle start when buffer is full.
else { break; }
} while (1);
@ -195,8 +195,8 @@ void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8
void mc_dwell(float seconds)
{
uint16_t i = floor(1000/DWELL_TIME_STEP*seconds);
plan_synchronize();
delay_ms(floor(1000*seconds-i*DWELL_TIME_STEP)); // Delay millisecond remainder
protocol_buffer_synchronize();
delay_ms(floor(1000*seconds-i*DWELL_TIME_STEP)); // Delay millisecond remainder.
while (i-- > 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; idx<N_AXIS; idx++) {
// Set up pull off targets and machine positions for limit switches homed in the negative
// direction, rather than the traditional positive. Leave non-homed positions as zero and
// do not move them.
// NOTE: settings.max_travel[] is stored as a negative value.
if (HOMING_LOCATE_CYCLE & bit(idx)) {
if ( settings.homing_dir_mask & get_direction_mask(idx) ) {
pulloff_target[idx] = settings.homing_pulloff+settings.max_travel[idx];
sys.position[idx] = lround(settings.max_travel[idx]*settings.steps_per_mm[idx]);
} else {
pulloff_target[idx] = -settings.homing_pulloff;
}
}
}
plan_sync_position(); // Sync planner position to home for pull-off move.
sys.state = STATE_IDLE; // Set system state to IDLE to complete motion and indicate homed.
mc_line(pulloff_target, settings.homing_seek_rate, false);
st_cycle_start(); // Move it. Nothing should be in the buffer except this motion.
plan_synchronize(); // Make sure the motion completes.
// NOTE: Stepper idle lock resumes normal functionality after cycle.
// Homing cycle complete! Setup system for normal operation.
// -------------------------------------------------------------------------------------
// The gcode parser position circumvented by the pull-off maneuver, so sync position now.
// Gcode parser position was circumvented by the limits_go_home() routine, so sync position now.
gc_sync_position();
// Set idle state after homing completes and before returning to main program.
sys.state = STATE_IDLE;
st_go_idle(); // Set idle state after homing completes
// If hard limits feature enabled, re-enable hard limits pin change register after homing cycle.
limits_init();
// Finished!
}
// 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 mc_auto_cycle_start() { if (sys.auto_start) { st_cycle_start(); } }
// Method to ready the system to reset by setting the runtime reset command and killing any
// active processes in the system. This also checks if a system reset is issued while Grbl
// is in a motion state. If so, kills the steppers and sets the system alarm to flag position
@ -320,8 +264,8 @@ void mc_reset()
// the steppers enabled by avoiding the go_idle call altogether, unless the motion state is
// violated, by which, all bets are off.
if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_HOMING)) {
sys.execute |= EXEC_ALARM; // Execute alarm state.
st_go_idle(); // Execute alarm force kills steppers. Position likely lost.
sys.execute |= EXEC_ALARM; // Flag main program to execute alarm state.
st_go_idle(); // Force kill steppers. Position has likely been lost.
}
}
}

View File

@ -44,7 +44,4 @@ void mc_homing_cycle();
// Performs system reset. If in motion state, kills all motion and sets system alarm.
void mc_reset();
// Executes the auto cycle feature, if enabled.
void mc_auto_cycle_start();
#endif

View File

@ -249,19 +249,6 @@ uint8_t plan_check_full_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 plan_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
}
}
/* Add a new linear movement to the buffer. target[N_AXIS] is the signed, absolute target position
in millimeters. Feed rate specifies the speed of the motion. If feed rate is inverted, the feed
rate is taken to mean "frequency" and would complete the operation in 1/feed_rate minutes.
@ -373,7 +360,8 @@ void plan_buffer_line(float *target, float feed_rate, uint8_t invert_feed_rate)
// NOTE: Computed without any expensive trig, sin() or acos(), by trig half angle identity of cos(theta).
float sin_theta_d2 = sqrt(0.5*(1.0-junction_cos_theta)); // Trig half angle identity. Always positive.
// TODO: Acceleration used in calculation needs to be limited by the minimum of the two junctions.
// TODO: Technically, the acceleration used in calculation needs to be limited by the minimum of the
// two junctions. However, this shouldn't be a significant problem except in extreme circumstances.
block->max_junction_speed_sqr = max( MINIMUM_JUNCTION_SPEED*MINIMUM_JUNCTION_SPEED,
(block->acceleration * settings.junction_deviation * sin_theta_d2)/(1.0-sin_theta_d2) );
}

View File

@ -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

View File

@ -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; } }

View File

@ -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

View File

@ -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) {

View File

@ -21,23 +21,21 @@
#include "system.h"
#include "spindle_control.h"
#include "planner.h"
#include "protocol.h"
void spindle_init()
{
SPINDLE_DIRECTION_DDR |= (1<<SPINDLE_DIRECTION_BIT);
{
// On the Uno, spindle enable and PWM are shared. Other CPUs have seperate enable pin.
#ifdef VARIABLE_SPINDLE
SPINDLE_PWM_DDR |= (1<<SPINDLE_PWM_BIT);
SPINDLE_PWM_DDR |= (1<<SPINDLE_PWM_BIT); // Configure as PWM output pin.
#ifndef CPU_MAP_ATMEGA328P
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT);
SPINDLE_ENABLE_DDR |= (1<<SPINDLE_ENABLE_BIT); // Configure as output pin.
#endif
#else
SPINDLE_ENABLE_PORT |= (1<<SPINDLE_ENABLE_BIT);
SPINDLE_ENABLE_DDR |= (1<<SPINDLE_ENABLE_BIT); // Configure as output pin.
#endif
SPINDLE_DIRECTION_DDR |= (1<<SPINDLE_DIRECTION_BIT); // Configure as output pin.
spindle_stop();
}
@ -48,10 +46,10 @@ void spindle_stop()
#ifdef VARIABLE_SPINDLE
TCCRA_REGISTER &= ~(1<<COMB_BIT); // Disable PWM. Output voltage is zero.
#ifndef CPU_MAP_ATMEGA328P
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT);
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT); // Set pin to low.
#endif
#else
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT);
SPINDLE_ENABLE_PORT &= ~(1<<SPINDLE_ENABLE_BIT); // Set pin to low.
#endif
}
@ -59,7 +57,7 @@ void spindle_stop()
void spindle_run(uint8_t direction, float rpm)
{
// Empty planner buffer to ensure spindle is set when programmed.
plan_synchronize();
protocol_buffer_synchronize();
// Halt or set spindle direction and rpm.
if (direction == SPINDLE_DISABLE) {
@ -67,7 +65,7 @@ void spindle_run(uint8_t direction, float rpm)
spindle_stop();
} else {
if (direction == SPINDLE_ENABLE_CW) {
SPINDLE_DIRECTION_PORT &= ~(1<<SPINDLE_DIRECTION_BIT);
} else {

291
stepper.c
View File

@ -27,18 +27,19 @@
// Some useful constants.
#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment
#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment
#define REQ_MM_INCREMENT_SCALAR 1.25
#define RAMP_ACCEL 0
#define RAMP_CRUISE 1
#define RAMP_DECEL 2
// Define AMASS levels and cutoff frequencies. The highest level frequency bin starts at 0Hz and
// ends at its cutoff frequency. The next lower level frequency bin starts at the next higher cutoff
// frequency, and so on. The cutoff frequencies for each level must be considered carefully against
// how much it over-drives the stepper ISR, the accuracy of the 16-bit timer, and the CPU overhead.
// Level 0 (no AMASS, normal operation) frequency bin starts at the Level 1 cutoff frequency and
// up to as fast as the CPU allows (over 30kHz in limited testing).
// NOTE: AMASS uutoff frequency multiplied by ISR overdrive factor must not exceed maximum step frequency.
// Define Adaptive Multi-Axis Step-Smoothing(AMASS) levels and cutoff frequencies. The highest level
// frequency bin starts at 0Hz and ends at its cutoff frequency. The next lower level frequency bin
// starts at the next higher cutoff frequency, and so on. The cutoff frequencies for each level must
// be considered carefully against how much it over-drives the stepper ISR, the accuracy of the 16-bit
// timer, and the CPU overhead. Level 0 (no AMASS, normal operation) frequency bin starts at the
// Level 1 cutoff frequency and up to as fast as the CPU allows (over 30kHz in limited testing).
// NOTE: AMASS cutoff frequency multiplied by ISR overdrive factor must not exceed maximum step frequency.
// NOTE: Current settings are set to overdrive the ISR to no more than 16kHz, balancing CPU overhead
// and timer accuracy. Do not alter these settings unless you know what you are doing.
#define MAX_AMASS_LEVEL 3
@ -121,14 +122,14 @@ typedef struct {
uint8_t st_block_index; // Index of stepper common data block being prepped
uint8_t flag_partial_block; // Flag indicating the last block completed. Time to load a new one.
float step_per_mm; // Current planner block step/millimeter conversion scalar
float steps_remaining;
float mm_per_step;
float minimum_mm;
float step_per_mm; // Current planner block step/millimeter conversion scalar
float req_mm_increment;
float dt_remainder;
uint8_t ramp_type; // Current segment ramp state
float mm_complete; // End of velocity profile from end of current planner block in (mm).
// NOTE: This value must coincide with a step(no mantissa) when converted.
float current_speed; // Current speed at the end of the segment buffer (mm/min)
float maximum_speed; // Maximum speed of executing block. Not always nominal speed. (mm/min)
float exit_speed; // Exit speed of executing block (mm/min)
@ -202,7 +203,6 @@ void st_wake_up()
#endif
// Enable Stepper Driver Interrupt
TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (1<<CS10); // Set prescaler
TIMSK1 |= (1<<OCIE1A);
}
}
@ -211,19 +211,20 @@ void st_wake_up()
// Stepper shutdown
void st_go_idle()
{
// Disable Timer1 Stepper Driver Interrupt. Allow Timer0 to finish. It will disable itself.
TIMSK1 &= ~(1<<OCIE1A);
// Disable Stepper Driver Interrupt. Allow Stepper Port Reset Interrupt to finish, if active.
TIMSK1 &= ~(1<<OCIE1A); // Disable Timer1 interrupt
TCCR1B = (TCCR1B & ~((1<<CS12) | (1<<CS11))) | (1<<CS10); // Reset clock to no prescaling.
busy = false;
// Set stepper driver idle state, disabled or enabled, depending on settings and circumstances.
bool pin_state = false;
bool pin_state = false; // Keep enabled.
if (((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) && sys.state != STATE_HOMING) {
// 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);
pin_state = true;
pin_state = true; // Override. Disable steppers.
}
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { pin_state = !pin_state; }
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { pin_state = !pin_state; } // Apply pin invert.
if (pin_state) { STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT); }
else { STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT); }
}
@ -250,10 +251,17 @@ void st_go_idle()
non-dominant Bresenham axes step in the intermediate ISR tick, while the dominant axis is
stepping every two ISR ticks, rather than every ISR tick in the traditional sense. At AMASS
Level 2, we simply bit-shift again, so the non-dominant Bresenham axes can step within any
of the four ISR ticks, and the dominant axis steps every four ISR ticks. And so on. This,
in effect, removes the vast majority of the multi-axis aliasing issues with the Bresenham
algorithm and does not significantly alter Grbl's performance, but in fact, more efficiently
utilizes unused CPU cycles overall throughout all configurations.
of the four ISR ticks, the dominant axis steps every four ISR ticks, and quadruple the
stepper ISR frequency. And so on. This, in effect, virtually eliminates multi-axis aliasing
issues with the Bresenham algorithm and does not significantly alter Grbl's performance, but
in fact, more efficiently utilizes unused CPU cycles overall throughout all configurations.
AMASS retains the Bresenham algorithm exactness by requiring that it always executes a full
Bresenham step, regardless of AMASS Level. Meaning that for an AMASS Level 2, all four
intermediate steps must be completed such that baseline Bresenham (Level 0) count is always
retained. Similarly, AMASS Level 3 means all eight intermediate steps must be executed.
Although the AMASS Levels are in reality arbitrary, where the baseline Bresenham counts can
be multiplied by any integer value, multiplication by powers of two are simply used to ease
CPU overhead with bitshift integer operations.
This interrupt is simple and dumb by design. All the computational heavy-lifting, as in
determining accelerations, is performed elsewhere. This interrupt pops pre-computed segments,
defined as constant velocity over n number of steps, from the step segment buffer and then
@ -265,6 +273,7 @@ void st_go_idle()
NOTE: This interrupt must be as efficient as possible and complete before the next ISR tick,
which for Grbl must be less than 33.3usec (@30kHz ISR rate). Oscilloscope measured time in
ISR is 5usec typical and 25usec maximum, well below requirement.
NOTE: This ISR expects at least one step to be executed per segment.
*/
// TODO: Replace direct updating of the int32 position counters in the ISR somehow. Perhaps use smaller
// int8 variables and update position counters only when a segment completes. This can get complicated
@ -279,15 +288,15 @@ ISR(TIMER1_COMPA_vect)
// Then pulse the stepping pins
#ifdef STEP_PULSE_DELAY
st.step_bits = (STEPPING_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.
st.step_bits = (STEP_PORT & ~STEP_MASK) | st.step_outbits; // Store out_bits to prevent overwriting.
#else // Normal operation
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | st.step_outbits;
STEP_PORT = (STEP_PORT & ~STEP_MASK) | st.step_outbits;
#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.
TCNT0 = st.step_pulse_time; // Reload Timer0 counter
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
TCCR0B = (1<<CS01); // Begin Timer0. Full speed, 1/8 prescaler
busy = true;
sei(); // Re-enable interrupts to allow Stepper Port Reset Interrupt to fire on-time.
@ -299,10 +308,13 @@ ISR(TIMER1_COMPA_vect)
if (segment_buffer_head != segment_buffer_tail) {
// Initialize new step segment and load number of steps to execute
st.exec_segment = &segment_buffer[segment_buffer_tail];
// Initialize step segment timing per step and load number of steps to execute.
#ifndef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING
// With AMASS is disabled, set timer prescaler for segments with slow step frequencies (< 250Hz).
TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (st.exec_segment->prescaler<<CS10);
#endif
// Initialize step segment timing per step and load number of steps to execute.
OCR1A = st.exec_segment->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<<SPINDLE_ENABLE_BIT;
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
}
/* 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
finish, if Timer2 is disabled after completing a move. */
// 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.
pulse. This should always trigger before the next Timer1 COMPA interrupt and independently
finish, if Timer1 is disabled after completing a move.
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.
*/
// This interrupt is enabled by ISR_TIMER1_COMPAREA when it sets the motor port bits to execute
// a step. This ISR resets the motor port after a short period (settings.pulse_microseconds)
// completing one step cycle.
ISR(TIMER0_OVF_vect)
{
// Reset stepping pins (leave the direction pins)
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.step_invert_mask & STEP_MASK);
STEP_PORT = (STEP_PORT & ~STEP_MASK) | (settings.step_invert_mask & STEP_MASK);
TCCR0B = 0; // Disable Timer0 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
@ -409,7 +425,7 @@ ISR(TIMER0_OVF_vect)
// st_wake_up() routine.
ISR(TIMER0_COMPA_vect)
{
STEPPING_PORT = st.step_bits; // Begin step pulse.
STEP_PORT = st.step_bits; // Begin step pulse.
}
#endif
@ -417,6 +433,9 @@ ISR(TIMER0_OVF_vect)
// Reset and clear stepper subsystem variables
void st_reset()
{
// Initialize stepper driver idle state.
st_go_idle();
memset(&prep, 0, sizeof(prep));
memset(&st, 0, sizeof(st));
st.exec_segment = NULL;
@ -426,9 +445,6 @@ void st_reset()
segment_buffer_head = 0; // empty = tail
segment_next_head = 1;
busy = false;
// Initialize stepper driver idle state.
st_go_idle();
}
@ -436,8 +452,8 @@ void st_reset()
void stepper_init()
{
// Configure step and direction interface pins
STEPPING_DDR |= STEP_MASK;
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | settings.step_invert_mask;
STEP_DDR |= STEP_MASK;
STEP_PORT = (STEP_PORT & ~STEP_MASK) | settings.step_invert_mask;
STEPPERS_DISABLE_DDR |= 1<<STEPPERS_DISABLE_BIT;
DIRECTION_DDR |= DIRECTION_MASK;
DIRECTION_PORT = (DIRECTION_PORT & ~DIRECTION_MASK) | settings.dir_invert_mask;
@ -445,15 +461,13 @@ void stepper_init()
// Configure Timer 1: Stepper Driver Interrupt
TCCR1B &= ~(1<<WGM13); // waveform generation = 0100 = CTC
TCCR1B |= (1<<WGM12);
TCCR1A &= ~(1<<WGM11);
TCCR1A &= ~(1<<WGM10);
TCCR1A &= ~(3<<COM1A0); // output mode = 00 (disconnected)
TCCR1A &= ~(3<<COM1B0);
TCCR1B = (TCCR1B & ~(0x07<<CS10)) | (1<<CS10);
// TIMSK1 &= ~(1<<OCIE1A); // Timer 1 disabled in st_go_idle().
TCCR1A &= ~((1<<WGM11) | (1<<WGM10));
TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0)); // Disconnect OC1 output
// TCCR1B = (TCCR1B & ~((1<<CS12) | (1<<CS11))) | (1<<CS10); // Set in st_go_idle().
// TIMSK1 &= ~(1<<OCIE1A); // Set in st_go_idle().
// Configure Timer 0: Stepper Port Reset Interrupt
TIMSK0 &= ~(1<<TOIE0);
TIMSK0 &= ~((1<<OCIE0B) | (1<<OCIE0A) | (1<<TOIE0)); // Disconnect OC0 outputs and OVF interrupt.
TCCR0A = 0; // Normal operation
TCCR0B = 0; // Disable Timer0 until needed
TIMSK0 |= (1<<TOIE0); // Enable Timer0 overflow interrupt
@ -463,44 +477,6 @@ void stepper_init()
}
// 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_prep_buffer(); // Initialize step segment buffer before beginning cycle.
st_wake_up();
if (bit_isfalse(settings.flags,BITFLAG_AUTO_START)) { sys.auto_start = false; } // Reset auto start per settings.
}
}
// 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;
st_update_plan_block_parameters();
st_prep_buffer();
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 (sys.state != STATE_QUEUED) {
sys.state = STATE_IDLE;
}
}
// Called by planner_recalculate() when the executing block is updated by the new plan.
void st_update_plan_block_parameters()
{
@ -522,41 +498,29 @@ void st_update_plan_block_parameters()
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-50 msec of steps.
NOTE: The segment buffer executes a computed number of steps over a configured segment
execution time period, except at an end of a planner block where the segment execution
gets truncated by the lack of travel distance. Since steps are integer values and to keep
the distance traveled over the segment exact, a fractional step remaining after the last
executed step in a segment is handled by allowing the stepper algorithm distance counters
to tick to this fractional value without triggering a full step. So, when the next segment
is loaded for execution, its first full step will already have the distance counters primed
with the previous segment fractional step and will execute exactly on time according to
the planner block velocity profile. This ensures the step phasing between segments are kept
in sync and prevents artificially created accelerations between segments if they are not
accounted for. This allows the stepper algorithm to run at very high step rates without
losing steps.
Currently, the segment buffer conservatively holds roughly up to 40-50 msec of steps.
NOTE: Computation units are in steps, millimeters, and minutes.
*/
void st_prep_buffer()
{
while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer.
if (sys.state == STATE_QUEUED) { return; } // Block until a motion state is issued
// Determine if we need to load a new planner block or if the block remainder is replanned.
// Determine if we need to load a new planner block or if the block has been replanned.
if (pl_block == NULL) {
pl_block = plan_get_current_block(); // Query planner for a queued block
if (pl_block == NULL) { return; } // No planner blocks. Exit.
// Check if the segment buffer completed the last planner block. If so, load the Bresenham
// data for the block. If not, we are still mid-block and the velocity profile has changed.
// data for the block. If not, we are still mid-block and the velocity profile was updated.
if (prep.flag_partial_block) {
prep.flag_partial_block = false; // Reset flag
} else {
// Increment stepper common data index
// Increment stepper common data index to store new planner block data.
if ( ++prep.st_block_index == (SEGMENT_BUFFER_SIZE-1) ) { prep.st_block_index = 0; }
// Prepare and copy Bresenham algorithm segment data from the new planner block, so that
// when the segment buffer completes the planner block, it may be discarded immediately.
// when the segment buffer completes the planner block, it may be discarded when the
// segment buffer finishes the prepped block, but the stepper ISR is still executing it.
st_prep_block = &st_block_buffer[prep.st_block_index];
st_prep_block->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);

View File

@ -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();

View File

@ -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, &parameter)) { return(STATUS_BAD_NUMBER_FORMAT); }
if(line[char_counter++] != '=') { return(STATUS_UNSUPPORTED_STATEMENT); }
if (helper_var) { // Store startup line