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:
parent
cc9afdc195
commit
50fbc6e297
15
README.md
15
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_
|
||||
|
34
config.h
34
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<<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
|
||||
|
@ -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);
|
||||
|
||||
|
10
cpu_map.h
10
cpu_map.h
@ -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
|
||||
|
17
gcode.c
17
gcode.c
@ -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
|
||||
@ -459,7 +458,7 @@ 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.
|
||||
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,
|
||||
|
2
gcode.h
2
gcode.h
@ -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
228
limits.c
@ -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,34 +68,15 @@ 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)
|
||||
{
|
||||
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 (bit_isfalse(sys.execute,EXEC_ALARM)) {
|
||||
uint8_t bits = LIMIT_PIN;
|
||||
if (bit_istrue(settings.flags,BITFLAG_INVERT_LIMIT_PINS)) { bits ^= LIMIT_MASK; }
|
||||
if (bits & LIMIT_MASK) {
|
||||
mc_reset(); // Initiate system kill.
|
||||
sys.execute |= EXEC_CRIT_EVENT; // Indicate hard limit critical event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
ISR(LIMIT_INT_vect)
|
||||
#ifndef ENABLE_SOFTWARE_DEBOUNCE
|
||||
ISR(LIMIT_INT_vect) // DEFAULT: Limit pin change interrupt process.
|
||||
{
|
||||
// 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
|
||||
@ -106,86 +90,158 @@ void limits_disable()
|
||||
}
|
||||
}
|
||||
}
|
||||
#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.
|
||||
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; }
|
||||
if (sys.abort) { return; } // Block if system reset has been issued.
|
||||
|
||||
uint8_t invert_pin;
|
||||
if (bit_isfalse(settings.flags,BITFLAG_INVERT_LIMIT_PINS)) { invert_pin = approach; }
|
||||
else { invert_pin = !approach; }
|
||||
// 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; }
|
||||
max_travel *= -HOMING_AXIS_SEARCH_SCALAR; // Ensure homing switches engaged by over-estimating 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;
|
||||
plan_reset(); // Reset planner buffer to zero planner current position and to clear previous motions.
|
||||
|
||||
do {
|
||||
// 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;
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
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.
|
||||
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.
|
||||
|
||||
// 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;
|
||||
// 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.
|
||||
|
||||
// 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); }
|
||||
}
|
||||
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.
|
||||
// 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.
|
||||
|
2
limits.h
2
limits.h
@ -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
14
main.c
@ -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(;;) {
|
||||
|
||||
// Reset the system primary functionality.
|
||||
// TODO: Separate configure task that require interrupts to be disabled, especially upon
|
||||
// a system abort and ensuring any active interrupts are cleanly reset.
|
||||
|
||||
// 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 */
|
||||
|
@ -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.
|
||||
// Homing cycle complete! Setup system for normal operation.
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
16
planner.c
16
planner.c
@ -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) );
|
||||
}
|
||||
|
@ -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
|
||||
|
247
protocol.c
247
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; } }
|
||||
|
23
protocol.h
23
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
|
||||
|
7
report.c
7
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) {
|
||||
|
@ -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) {
|
||||
|
277
stepper.c
277
stepper.c
@ -28,17 +28,18 @@
|
||||
|
||||
// Some useful constants.
|
||||
#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()
|
||||
{
|
||||
@ -523,40 +499,28 @@ void st_update_plan_block_parameters()
|
||||
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.
|
||||
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;
|
||||
@ -641,6 +611,7 @@ void st_prep_buffer()
|
||||
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 {
|
||||
@ -725,8 +713,9 @@ 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,14 +724,25 @@ 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
|
||||
|
||||
float inv_rate = dt/(prep.steps_remaining-steps_remaining);
|
||||
cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step)
|
||||
// Compute CPU cycles per step for the prepped segment.
|
||||
uint32_t 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.
|
||||
@ -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.
|
||||
// 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.
|
||||
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);
|
||||
|
@ -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();
|
||||
|
||||
|
61
system.c
61
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
|
||||
|
Loading…
Reference in New Issue
Block a user