Merge branch 'dev_2' into dev

Conflicts:
README.md
gcode.c
motion_control.c
planner.c
planner.h
protocol.c
report.c
settings.c
settings.h
stepper.c
stepper.h
This commit is contained in:
Sonny Jeon 2013-10-29 19:10:39 -06:00
parent b06643a2e0
commit 4f9bcde40e
33 changed files with 3524 additions and 1029 deletions

View File

@ -84,7 +84,7 @@ main.elf: $(OBJECTS)
grbl.hex: main.elf grbl.hex: main.elf
rm -f grbl.hex rm -f grbl.hex
avr-objcopy -j .text -j .data -O ihex main.elf grbl.hex avr-objcopy -j .text -j .data -O ihex main.elf grbl.hex
avr-size -C --mcu=$(DEVICE) main.elf avr-size --format=berkeley main.elf
# If you have an EEPROM section, you must also create a hex file for the # If you have an EEPROM section, you must also create a hex file for the
# EEPROM and add it to the "flash" target. # EEPROM and add it to the "flash" target.

View File

@ -2,3 +2,28 @@
------------ ------------
This branch serves only as a developmental platform for working on new ideas that may eventually be installed into Grbl's edge branch. Please do not use as there is no guarantee this code base is up-to-date or working. This branch serves only as a developmental platform for working on new ideas that may eventually be installed into Grbl's edge branch. Please do not use as there is no guarantee this code base is up-to-date or working.
------------
Grbl is a no-compromise, high performance, low cost alternative to parallel-port-based motion control for CNC milling. It will run on a vanilla Arduino (Duemillanove/Uno) as long as it sports an Atmega 328.
The controller is written in highly optimized C utilizing every clever feature of the AVR-chips to achieve precise timing and asynchronous operation. It is able to m aintain more than 30kHz of stable, jitter free control pulses.
It accepts standards-compliant G-code and has been tested with the output of several CAM tools with no problems. Arcs, circles and helical motion are fully supported, as well as, other basic functional g-code commands. Functions and variables are not currently supported, but may be included in future releases in a form of a pre-processor.
Grbl includes full acceleration management with look ahead. That means the controller will look up to 18 motions into the future and plan its velocities ahead to deliver smooth acceleration and jerk-free cornering.
##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: Multiple changes to increase planner execution speed and removed redundant variables.
- 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.
- 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.
- 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. Also reduces soft limits CPU overhead.
- 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.
_The project was initially inspired by the Arduino GCode Interpreter by Mike Ellery_

146
config.h
View File

@ -2,8 +2,8 @@
config.h - compile time configuration config.h - compile time configuration
Part of Grbl Part of Grbl
Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -19,80 +19,24 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>. along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef config_h // This file contains compile-time configurations for Grbl's internal system. For the most part,
#define config_h // users will not need to directly modify these, but they are here for specific needs, i.e.
// performance tuning or adjusting to non-typical machines.
// IMPORTANT: Any changes here requires a full re-compiling of the source code to propagate them. // IMPORTANT: Any changes here requires a full re-compiling of the source code to propagate them.
#ifndef config_h
#define config_h
// Default settings. Used when resetting EEPROM. Change to desired name in defaults.h // Default settings. Used when resetting EEPROM. Change to desired name in defaults.h
#define DEFAULTS_GENERIC #define DEFAULTS_ZEN_TOOLWORKS_7x7
// Serial baud rate // Serial baud rate
#define BAUD_RATE 9600 #define BAUD_RATE 115200
// Define pin-assignments // Default pin mappings. Grbl officially supports the Arduino Uno only. Other processor types
// NOTE: All step bit and direction pins must be on the same port. // may exist from user-supplied templates or directly user-defined in pin_map.h
#define STEPPING_DDR DDRD #define PIN_MAP_ARDUINO_UNO
#define STEPPING_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
#define X_DIRECTION_BIT 5 // Uno Digital Pin 5
#define Y_DIRECTION_BIT 6 // Uno Digital Pin 6
#define Z_DIRECTION_BIT 7 // Uno Digital Pin 7
#define STEP_MASK ((1<<X_STEP_BIT)|(1<<Y_STEP_BIT)|(1<<Z_STEP_BIT)) // All step bits
#define DIRECTION_MASK ((1<<X_DIRECTION_BIT)|(1<<Y_DIRECTION_BIT)|(1<<Z_DIRECTION_BIT)) // All direction bits
#define STEPPING_MASK (STEP_MASK | DIRECTION_MASK) // All stepping-related bits (step/direction)
#define STEPPERS_DISABLE_DDR DDRB
#define STEPPERS_DISABLE_PORT PORTB
#define STEPPERS_DISABLE_BIT 0 // Uno Digital Pin 8
#define STEPPERS_DISABLE_MASK (1<<STEPPERS_DISABLE_BIT)
// NOTE: All limit bit pins must be on the same port
#define LIMIT_DDR DDRB
#define LIMIT_PIN PINB
#define LIMIT_PORT PORTB
#define X_LIMIT_BIT 1 // Uno Digital Pin 9
#define Y_LIMIT_BIT 2 // Uno Digital Pin 10
#define Z_LIMIT_BIT 3 // Uno Digital Pin 11
#define LIMIT_INT PCIE0 // Pin change interrupt enable pin
#define LIMIT_INT_vect PCINT0_vect
#define LIMIT_PCMSK PCMSK0 // Pin change interrupt register
#define LIMIT_MASK ((1<<X_LIMIT_BIT)|(1<<Y_LIMIT_BIT)|(1<<Z_LIMIT_BIT)) // All limit bits
#define SPINDLE_ENABLE_DDR DDRB
#define SPINDLE_ENABLE_PORT PORTB
#define SPINDLE_ENABLE_BIT 4 // Uno Digital Pin 12
#define SPINDLE_DIRECTION_DDR DDRB
#define SPINDLE_DIRECTION_PORT PORTB
#define SPINDLE_DIRECTION_BIT 5 // Uno Digital Pin 13 (NOTE: D13 can't be pulled-high input due to LED.)
#define COOLANT_FLOOD_DDR DDRC
#define COOLANT_FLOOD_PORT PORTC
#define COOLANT_FLOOD_BIT 3 // Uno Analog Pin 3
// NOTE: Uno analog pins 4 and 5 are reserved for an i2c interface, and may be installed at
// a later date if flash and memory space allows.
// #define ENABLE_M7 // Mist coolant disabled by default. Uncomment to enable.
#ifdef ENABLE_M7
#define COOLANT_MIST_DDR DDRC
#define COOLANT_MIST_PORT PORTC
#define COOLANT_MIST_BIT 4 // Uno Analog Pin 4
#endif
// NOTE: All pinouts pins must be on the same port
#define PINOUT_DDR DDRC
#define PINOUT_PIN PINC
#define PINOUT_PORT PORTC
#define PIN_RESET 0 // Uno Analog Pin 0
#define PIN_FEED_HOLD 1 // Uno Analog Pin 1
#define PIN_CYCLE_START 2 // Uno Analog Pin 2
#define PINOUT_INT PCIE1 // Pin change interrupt enable pin
#define PINOUT_INT_vect PCINT1_vect
#define PINOUT_PCMSK PCMSK1 // Pin change interrupt register
#define PINOUT_MASK ((1<<PIN_RESET)|(1<<PIN_FEED_HOLD)|(1<<PIN_CYCLE_START))
// Define runtime command special characters. These characters are 'picked-off' directly from the // Define runtime command special characters. These characters are 'picked-off' directly from the
// serial read data stream and are not passed to the grbl line execution parser. Select characters // serial read data stream and are not passed to the grbl line execution parser. Select characters
@ -105,15 +49,14 @@
#define CMD_CYCLE_START '~' #define CMD_CYCLE_START '~'
#define CMD_RESET 0x18 // ctrl-x #define CMD_RESET 0x18 // ctrl-x
// The "Stepper Driver Interrupt" employs the Pramod Ranade inverse time algorithm to manage the // The "Stepper Driver Interrupt" employs an inverse time algorithm to manage the Bresenham line
// Bresenham line stepping algorithm. The value ISR_TICKS_PER_SECOND is the frequency(Hz) at which // stepping algorithm. The value ISR_TICKS_PER_SECOND is the frequency(Hz) at which the inverse time
// the Ranade algorithm ticks at. Maximum step frequencies are limited by the Ranade frequency by // algorithm ticks at. Recommended step frequencies are limited by the inverse time frequency by
// approximately 0.75-0.9 * ISR_TICK_PER_SECOND. Meaning for 20kHz, the max step frequency is roughly // approximately 0.75-0.9 * ISR_TICK_PER_SECOND. Meaning for 30kHz, the max step frequency is roughly
// 15-18kHz. An Arduino can safely complete a single interrupt of the current stepper driver algorithm // 22.5-27kHz, but 30kHz is still possible, just not optimal. An Arduino can safely complete a single
// theoretically up to a frequency of 35-40kHz, but CPU overhead increases exponentially as this // interrupt of the current stepper driver algorithm theoretically up to a frequency of 35-40kHz, but
// frequency goes up. So there will be little left for other processes like arcs. // CPU overhead increases exponentially as this frequency goes up. So there will be little left for
// In future versions, more work will be done to increase the step rates but still stay around // other processes like arcs.
// 20kHz by performing two steps per step event, rather than just one.
#define ISR_TICKS_PER_SECOND 30000L // Integer (Hz) #define ISR_TICKS_PER_SECOND 30000L // Integer (Hz)
// The temporal resolution of the acceleration management subsystem. Higher number give smoother // The temporal resolution of the acceleration management subsystem. Higher number give smoother
@ -122,36 +65,41 @@
// profiles and how the stepper program actually performs them. The correct value for this parameter // profiles and how the stepper program actually performs them. The correct value for this parameter
// is machine dependent, so it's advised to set this only as high as needed. Approximate successful // is machine dependent, so it's advised to set this only as high as needed. Approximate successful
// values can widely range from 50 to 200 or more. Cannot be greater than ISR_TICKS_PER_SECOND/2. // values can widely range from 50 to 200 or more. Cannot be greater than ISR_TICKS_PER_SECOND/2.
// NOTE: Ramp count variable type in stepper module may need to be updated if changed.
#define ACCELERATION_TICKS_PER_SECOND 120L #define ACCELERATION_TICKS_PER_SECOND 120L
// NOTE: Make sure this value is less than 256, when adjusting both dependent parameters. // NOTE: Make sure this value is less than 256, when adjusting both dependent parameters.
#define ISR_TICKS_PER_ACCELERATION_TICK (ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND) #define ISR_TICKS_PER_ACCELERATION_TICK (ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND)
// The Ranade algorithm can use either floating point or long integers for its counters, but for // The inverse time algorithm can use either floating point or long integers for its counters (usually
// integers the counter values must be scaled since these values can be very small (10^-6). This // very small values ~10^-6), but with integers, the counter values must be scaled to be greater than
// multiplier value scales the floating point counter values for use in a long integer. Long integers // one. This multiplier value scales the floating point counter values for use in a long integer, which
// are finite so select the multiplier value high enough to avoid any numerical round-off issues and // are significantly faster to compute with a slightly higher precision ceiling than floats. Long
// still have enough range to account for all motion types. However, in most all imaginable CNC // integers are finite so select the multiplier value high enough to avoid any numerical round-off
// applications, the following multiplier value will work more than well enough. If you do have // issues and still have enough range to account for all motion types. However, in most all imaginable
// CNC applications, the following multiplier value will work more than well enough. If you do have
// happened to weird stepper motion issues, try modifying this value by adding or subtracting a // happened to weird stepper motion issues, try modifying this value by adding or subtracting a
// zero and report it to the Grbl administrators. // zero and report it to the Grbl administrators.
#define RANADE_MULTIPLIER 100000000.0 #define INV_TIME_MULTIPLIER 10000000.0
// Minimum planner junction speed. Sets the default minimum speed the planner plans for at the end
// of the buffer and all stops. This should not be much greater than zero and should only be changed
// if unwanted behavior is observed on a user's machine when running at very slow speeds.
#define MINIMUM_PLANNER_SPEED 0.0 // (mm/min)
// Minimum stepper rate for the "Stepper Driver Interrupt". Sets the absolute minimum stepper rate // Minimum stepper rate for the "Stepper Driver Interrupt". Sets the absolute minimum stepper rate
// in the stepper program and never runs slower than this value. If the RANADE_MULTIPLIER value // in the stepper program and never runs slower than this value. If the INVE_TIME_MULTIPLIER value
// changes, it will affect how this value works. So, if a zero is add/subtracted from the // changes, it will affect how this value works. So, if a zero is add/subtracted from the
// RANADE_MULTIPLIER value, do the same to this value if you want to same response. // INV_TIME_MULTIPLIER value, do the same to this value if you want to same response.
// NOTE: Compute by (desired_step_rate/60) * RANADE_MULTIPLIER/ISR_TICKS_PER_SECOND. (mm/min) // NOTE: Compute by (desired_step_rate/60) * INV_TIME_MULTIPLIER/ISR_TICKS_PER_SECOND. (mm/min)
#define MINIMUM_STEP_RATE 1000L // Integer (mult*mm/isr_tic) #define MINIMUM_STEP_RATE 1000L // Integer (mult*mm/isr_tic)
// Minimum stepper rate. Only used by homing at this point. May be removed in later releases. // Minimum stepper rate. Only used by homing at this point. May be removed in later releases.
#define MINIMUM_STEPS_PER_MINUTE 800 // (steps/min) - Integer value only #define MINIMUM_STEPS_PER_MINUTE 800 // (steps/min) - Integer value only
// Minimum planner junction speed. Sets the default minimum junction speed the planner plans to at
// every buffer block junction, except for starting from rest and end of the buffer, which are always
// zero. This value controls how fast the machine moves through junctions with no regard for acceleration
// limits or angle between neighboring block line move directions. This is useful for machines that can't
// tolerate the tool dwelling for a split second, i.e. 3d printers or laser cutters. If used, this value
// should not be much greater than zero or to the minimum value necessary for the machine to work.
#define MINIMUM_JUNCTION_SPEED 0.0 // (mm/min)
// Time delay increments performed during a dwell. The default value is set at 50ms, which provides // Time delay increments performed during a dwell. The default value is set at 50ms, which provides
// a maximum time delay of roughly 55 minutes, more than enough for most any application. Increasing // a maximum time delay of roughly 55 minutes, more than enough for most any application. Increasing
// this delay will increase the maximum dwell time linearly, but also reduces the responsiveness of // this delay will increase the maximum dwell time linearly, but also reduces the responsiveness of
@ -208,7 +156,7 @@
// The number of linear motions in the planner buffer to be planned at any give time. The vast // The number of linear motions in the planner buffer to be planned at any give time. The vast
// majority of RAM that Grbl uses is based on this buffer size. Only increase if there is extra // majority of RAM that Grbl uses is based on this buffer size. Only increase if there is extra
// available RAM, like when re-compiling for a Teensy or Sanguino. Or decrease if the Arduino // available RAM, like when re-compiling for a Mega or Sanguino. Or decrease if the Arduino
// begins to crash due to the lack of available RAM or if the CPU is having trouble keeping // begins to crash due to the lack of available RAM or if the CPU is having trouble keeping
// up with planning new incoming motions as they are executed. // up with planning new incoming motions as they are executed.
// #define BLOCK_BUFFER_SIZE 18 // Uncomment to override default in planner.h. // #define BLOCK_BUFFER_SIZE 18 // Uncomment to override default in planner.h.
@ -217,11 +165,11 @@
// each of the startup blocks, as they are each stored as a string of this size. Make sure // each of the startup blocks, as they are each stored as a string of this size. Make sure
// to account for the available EEPROM at the defined memory address in settings.h and for // to account for the available EEPROM at the defined memory address in settings.h and for
// the number of desired startup blocks. // the number of desired startup blocks.
// NOTE: 50 characters is not a problem except for extreme cases, but the line buffer size // NOTE: 70 characters is not a problem except for extreme cases, but the line buffer size
// can be too small and g-code blocks can get truncated. Officially, the g-code standards // can be too small and g-code blocks can get truncated. Officially, the g-code standards
// support up to 256 characters. In future versions, this default will be increased, when // support up to 256 characters. In future versions, this default will be increased, when
// we know how much extra memory space we can re-invest into this. // we know how much extra memory space we can re-invest into this.
// #define LINE_BUFFER_SIZE 50 // Uncomment to override default in protocol.h // #define LINE_BUFFER_SIZE 70 // Uncomment to override default in protocol.h
// Serial send and receive buffer size. The receive buffer is often used as another streaming // Serial send and receive buffer size. The receive buffer is often used as another streaming
// buffer to store incoming blocks to be processed by Grbl when its ready. Most streaming // buffer to store incoming blocks to be processed by Grbl when its ready. Most streaming
@ -246,4 +194,12 @@
// TODO: Install compile-time option to send numeric status codes rather than strings. // TODO: Install compile-time option to send numeric status codes rather than strings.
// ---------------------------------------------------------------------------------------
// COMPILE-TIME ERROR CHECKING OF DEFINE VALUES:
#if (ISR_TICKS_PER_ACCELERATION_TICK > 255)
#error Parameters ACCELERATION_TICKS / ISR_TICKS must be < 256 to prevent integer overflow.
#endif
// ---------------------------------------------------------------------------------------
#endif #endif

View File

@ -2,7 +2,7 @@
defaults.h - defaults settings configuration file defaults.h - defaults settings configuration file
Part of Grbl Part of Grbl
Copyright (c) 2012 Sungeun K. Jeon Copyright (c) 2012-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -42,6 +42,7 @@
#define DEFAULT_REPORT_INCHES 0 // false #define DEFAULT_REPORT_INCHES 0 // false
#define DEFAULT_AUTO_START 1 // true #define DEFAULT_AUTO_START 1 // true
#define DEFAULT_INVERT_ST_ENABLE 0 // false #define DEFAULT_INVERT_ST_ENABLE 0 // false
#define DEFAULT_SOFT_LIMIT_ENABLE 0 // false
#define DEFAULT_HARD_LIMIT_ENABLE 0 // false #define DEFAULT_HARD_LIMIT_ENABLE 0 // false
#define DEFAULT_HOMING_ENABLE 0 // false #define DEFAULT_HOMING_ENABLE 0 // false
#define DEFAULT_HOMING_DIR_MASK 0 // move positive dir #define DEFAULT_HOMING_DIR_MASK 0 // move positive dir
@ -49,8 +50,11 @@
#define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm
#define DEFAULT_Z_MAX_TRAVEL 200 // mm
#endif #endif
#ifdef DEFAULTS_SHERLINE_5400 #ifdef DEFAULTS_SHERLINE_5400
@ -72,6 +76,7 @@
#define DEFAULT_REPORT_INCHES 1 // false #define DEFAULT_REPORT_INCHES 1 // false
#define DEFAULT_AUTO_START 1 // true #define DEFAULT_AUTO_START 1 // true
#define DEFAULT_INVERT_ST_ENABLE 0 // false #define DEFAULT_INVERT_ST_ENABLE 0 // false
#define DEFAULT_SOFT_LIMIT_ENABLE 0 // false
#define DEFAULT_HARD_LIMIT_ENABLE 0 // false #define DEFAULT_HARD_LIMIT_ENABLE 0 // false
#define DEFAULT_HOMING_ENABLE 0 // false #define DEFAULT_HOMING_ENABLE 0 // false
#define DEFAULT_HOMING_DIR_MASK 0 // move positive dir #define DEFAULT_HOMING_DIR_MASK 0 // move positive dir
@ -79,8 +84,11 @@
#define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm
#define DEFAULT_Z_MAX_TRAVEL 200 // mm
#endif #endif
#ifdef DEFAULTS_SHAPEOKO #ifdef DEFAULTS_SHAPEOKO
@ -105,6 +113,7 @@
#define DEFAULT_REPORT_INCHES 0 // false #define DEFAULT_REPORT_INCHES 0 // false
#define DEFAULT_AUTO_START 1 // true #define DEFAULT_AUTO_START 1 // true
#define DEFAULT_INVERT_ST_ENABLE 0 // false #define DEFAULT_INVERT_ST_ENABLE 0 // false
#define DEFAULT_SOFT_LIMIT_ENABLE 0 // false
#define DEFAULT_HARD_LIMIT_ENABLE 0 // false #define DEFAULT_HARD_LIMIT_ENABLE 0 // false
#define DEFAULT_HOMING_ENABLE 0 // false #define DEFAULT_HOMING_ENABLE 0 // false
#define DEFAULT_HOMING_DIR_MASK 0 // move positive dir #define DEFAULT_HOMING_DIR_MASK 0 // move positive dir
@ -112,8 +121,11 @@
#define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 25.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 255 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 255 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm
#define DEFAULT_Z_MAX_TRAVEL 200 // mm
#endif #endif
#ifdef DEFAULTS_ZEN_TOOLWORKS_7x7 #ifdef DEFAULTS_ZEN_TOOLWORKS_7x7
@ -128,14 +140,15 @@
#define DEFAULT_Z_STEPS_PER_MM (STEPS_PER_REV*MICROSTEPS/MM_PER_REV) #define DEFAULT_Z_STEPS_PER_MM (STEPS_PER_REV*MICROSTEPS/MM_PER_REV)
#define DEFAULT_STEP_PULSE_MICROSECONDS 10 #define DEFAULT_STEP_PULSE_MICROSECONDS 10
#define DEFAULT_ARC_TOLERANCE 0.005 // mm #define DEFAULT_ARC_TOLERANCE 0.005 // mm
#define DEFAULT_RAPID_FEEDRATE 2500.0 // mm/min #define DEFAULT_RAPID_FEEDRATE 4000.0 // mm/min
#define DEFAULT_FEEDRATE 1000.0 // mm/min #define DEFAULT_FEEDRATE 1000.0 // mm/min
#define DEFAULT_ACCELERATION 150.0*60*60 // 150 mm/min^2 #define DEFAULT_ACCELERATION 400.0*60*60 // 150 mm/min^2
#define DEFAULT_JUNCTION_DEVIATION 0.05 // mm #define DEFAULT_JUNCTION_DEVIATION 0.05 // mm
#define DEFAULT_STEPPING_INVERT_MASK (1<<Y_DIRECTION_BIT) #define DEFAULT_STEPPING_INVERT_MASK (1<<Y_DIRECTION_BIT)
#define DEFAULT_REPORT_INCHES 0 // false #define DEFAULT_REPORT_INCHES 0 // false
#define DEFAULT_AUTO_START 1 // true #define DEFAULT_AUTO_START 1 // true
#define DEFAULT_INVERT_ST_ENABLE 0 // false #define DEFAULT_INVERT_ST_ENABLE 0 // false
#define DEFAULT_SOFT_LIMIT_ENABLE 0 // false
#define DEFAULT_HARD_LIMIT_ENABLE 0 // false #define DEFAULT_HARD_LIMIT_ENABLE 0 // false
#define DEFAULT_HOMING_ENABLE 0 // false #define DEFAULT_HOMING_ENABLE 0 // false
#define DEFAULT_HOMING_DIR_MASK 0 // move positive dir #define DEFAULT_HOMING_DIR_MASK 0 // move positive dir
@ -143,8 +156,11 @@
#define DEFAULT_HOMING_FEEDRATE 50.0 // mm/min #define DEFAULT_HOMING_FEEDRATE 50.0 // mm/min
#define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k) #define DEFAULT_HOMING_DEBOUNCE_DELAY 100 // msec (0-65k)
#define DEFAULT_HOMING_PULLOFF 1.0 // mm #define DEFAULT_HOMING_PULLOFF 1.0 // mm
#define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-255) #define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-254, 255 keeps steppers enabled)
#define DEFAULT_DECIMAL_PLACES 3 #define DEFAULT_DECIMAL_PLACES 3
#define DEFAULT_X_MAX_TRAVEL 200 // mm
#define DEFAULT_Y_MAX_TRAVEL 200 // mm
#define DEFAULT_Z_MAX_TRAVEL 200 // mm
#endif #endif
#endif #endif

97
doc/pinmapping.txt Normal file
View File

@ -0,0 +1,97 @@
Mega328P Arduino Pin Mapping
============================
Digital 0 PD0 (RX)
Digital 1 PD1 (TX)
Digital 2 PD2
Digital 3 PD3
Digital 4 PD4
Digital 5 PD5
Digital 6 PD6
Digital 7 PD7
Digital 8 PB0
Digital 9 PB1
Digital 10 PB2
Digital 11 PB3 (MOSI)
Digital 12 PB4 (MISO)
Digital 13 PB5 (SCK)
Analog 0 PC0
Analog 1 PC1
Analog 2 PC2
Analog 3 PC3
Analog 4 PC4
Mega2560 Arduino Pin Mapping
============================
Digital pin 22 PA0 ( AD0 )
Digital pin 23 PA1 ( AD1 )
Digital pin 24 PA2 ( AD2 )
Digital pin 25 PA3 ( AD3 )
Digital pin 26 PA4 ( AD4 )
Digital pin 27 PA5 ( AD5 )
Digital pin 28 PA6 ( AD6 )
Digital pin 29 PA7 ( AD7 )
Digital pin 53 (PWM)(RX1) PB0 ( SS/PCINT0 )
Digital pin 52 (PWM)(SDA) PB1 ( SCK/PCINT1 )
Digital pin 51 (PWM)(SCL) PB2 ( MOSI/PCINT2 )
Digital pin 50 PB3 ( MISO/PCINT3 )
Digital pin 10 (PWM) PB4 ( OC2A/PCINT4 )
Digital pin 11 (PWM) PB5 ( OC1A/PCINT5 )
Digital pin 12 (PWM) PB6 ( OC1B/PCINT6 )
Digital pin 13 (PWM) PB7 ( OC0A/OC1C/PCINT7 )
Digital pin 37 PC0 ( A8 )
Digital pin 36 PC1 ( A9 )
Digital pin 35 PC2 ( A10 )
Digital pin 34 PC3 ( A11 )
Digital pin 33 PC4 ( A12 )
Digital pin 32 PC5 ( A13 )
Digital pin 31 PC6 ( A14 )
Digital pin 30 PC7 ( A15 )
Digital pin 21 (SCL) PD0 ( SCL/INT0 )
Digital pin 20 (SDA) PD1 ( SDA/INT1 )
Digital pin 19 PD2 ( RXDI/INT2 )
Digital pin 18 PD3 ( TXD1/INT3 )
Digital pin 38 PD7 ( T0 )
Digital pin 0 (PWM) (RX0) PE0 ( RXD0/PCINT8 )
Digital pin 1 (PWM) (TX0) PE1 ( TXD0 )
Digital pin 5 (PWM) PE3 ( OC3A/AIN1 )
Digital pin 2 (PWM) PE4 ( OC3B/INT4 )
Digital pin 3 (PWM) PE5 ( OC3C/INT5 )
Analog pin 0 PF0 ( ADC0 )
Analog pin 1 PF1 ( ADC1 )
Analog pin 2 PF2 ( ADC2 )
Analog pin 3 PF3 ( ADC3 )
Analog pin 4 PF4 ( ADC4/TMK )
Analog pin 5 PF5 ( ADC5/TMS )
Analog pin 6 PF6 ( ADC6/PCINT14 )
Analog pin 7 PF7 ( ADC7/PCINT15 )
Digital pin 41 PG0 ( WR )
Digital pin 40 PG1 ( RD )
Digital pin 39 PG2 ( ALE )
Digital pin 4 (PWM) PG5 ( OC0B )
Digital pin 17 (PWM) PH0 ( RXD2 )
Digital pin 16 (PWM) PH1 ( TXD2 )
Digital pin 6 (PWM)(RX3 ) PH3 ( OC4A )
Digital pin 7 (PWM)(TX2) PH4 ( OC4B )
Digital pin 8 (PWM)(RX2 ) PH5 ( OC4C )
Digital pin 9 (PWM)(TX1) PH6 ( OC2B )
Digital pin 15 PJ0 ( RXD3/PCINT9 )
Digital pin 14 PJ1 ( TXD3/PCINT10 )
Analog pin 8 PK0 ( ADC8/PCINT16 )
Analog pin 9 PK1 ( ADC9/PCINT17 )
Analog pin 10 PK2 ( ADC10/PCINT18 )
Analog pin 11 PK3 ( ADC11/PCINT19 )
Analog pin 12 PK4 ( ADC12/PCINT20 )
Analog pin 13 PK5 ( ADC13/PCINT21 )
Analog pin 14 PK6 ( ADC14/PCINT22 )
Analog pin 15 PK7 ( ADC15/PCINT23 )
Digital pin 49 PL0 ( ICP4 )
Digital pin 48 PL1 ( ICP5 )
Digital pin 47 PL2 ( T5 )
Digital pin 46 (PWM) PL3 ( OC5A )
Digital pin 45 (PWM) PL4 ( OC5B )
Digital pin 44 (PWM) PL5 ( OC5C )
Digital pin 43 PL6
Digital pin 42 PL7

View File

@ -2,7 +2,7 @@
#define eeprom_h #define eeprom_h
char eeprom_get_char(unsigned int addr); char eeprom_get_char(unsigned int addr);
void eeprom_put_char(unsigned int addr, char new_value); void eeprom_put_char(unsigned int addr, unsigned char new_value);
void memcpy_to_eeprom_with_checksum(unsigned int destination, char *source, unsigned int size); void memcpy_to_eeprom_with_checksum(unsigned int destination, char *source, unsigned int size);
int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size); int memcpy_from_eeprom_with_checksum(char *destination, unsigned int source, unsigned int size);

268
gcode.c
View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -39,7 +39,9 @@ parser_state_t gc;
#define FAIL(status) gc.status_code = status; #define FAIL(status) gc.status_code = status;
static int next_statement(char *letter, float *float_ptr, char *line, uint8_t *char_counter); static uint8_t next_statement(char *letter, float *float_ptr, char *line, uint8_t *char_counter);
static void gc_convert_arc_radius_mode(float *target) __attribute__((noinline));
static void select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2) static void select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
{ {
@ -48,6 +50,7 @@ static void select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
gc.plane_axis_2 = axis_2; gc.plane_axis_2 = axis_2;
} }
void gc_init() void gc_init()
{ {
memset(&gc, 0, sizeof(gc)); memset(&gc, 0, sizeof(gc));
@ -61,24 +64,29 @@ void gc_init()
} }
} }
// Sets g-code parser position in mm. Input in steps. Called by the system abort and hard // Sets g-code parser position in mm. Input in steps. Called by the system abort and hard
// limit pull-off routines. // limit pull-off routines.
void gc_set_current_position(int32_t x, int32_t y, int32_t z) void gc_sync_position(int32_t x, int32_t y, int32_t z)
{ {
gc.position[X_AXIS] = x/settings.steps_per_mm[X_AXIS]; uint8_t i;
gc.position[Y_AXIS] = y/settings.steps_per_mm[Y_AXIS]; for (i=0; i<N_AXIS; i++) {
gc.position[Z_AXIS] = z/settings.steps_per_mm[Z_AXIS]; gc.position[i] = sys.position[i]/settings.steps_per_mm[i];
} }
}
static float to_millimeters(float value) static float to_millimeters(float value)
{ {
return(gc.inches_mode ? (value * MM_PER_INCH) : value); return(gc.inches_mode ? (value * MM_PER_INCH) : value);
} }
// Executes one line of 0-terminated G-Code. The line is assumed to contain only uppercase // Executes one line of 0-terminated G-Code. The line is assumed to contain only uppercase
// characters and signed floating point values (no whitespace). Comments and block delete // characters and signed floating point values (no whitespace). Comments and block delete
// characters have been removed. All units and positions are converted and exported to grbl's // characters have been removed. In this function, all units and positions are converted and
// internal functions in terms of (mm, mm/min) and absolute machine coordinates, respectively. // exported to grbl's internal functions in terms of (mm, mm/min) and absolute machine
// coordinates, respectively.
uint8_t gc_execute_line(char *line) uint8_t gc_execute_line(char *line)
{ {
@ -98,9 +106,11 @@ uint8_t gc_execute_line(char *line)
uint8_t absolute_override = false; // true(1) = absolute motion for this block only {G53} uint8_t absolute_override = false; // true(1) = absolute motion for this block only {G53}
uint8_t non_modal_action = NON_MODAL_NONE; // Tracks the actions of modal group 0 (non-modal) uint8_t non_modal_action = NON_MODAL_NONE; // Tracks the actions of modal group 0 (non-modal)
float target[N_AXIS], offset[N_AXIS]; float target[N_AXIS];
clear_vector(target); // XYZ(ABC) axes parameters. clear_vector(target); // XYZ(ABC) axes parameters.
clear_vector(offset); // IJK Arc offsets are incremental. Value of zero indicates no change.
gc.arc_radius = 0;
clear_vector(gc.arc_offset); // IJK Arc offsets are incremental. Value of zero indicates no change.
gc.status_code = STATUS_OK; gc.status_code = STATUS_OK;
@ -203,8 +213,11 @@ uint8_t gc_execute_line(char *line)
/* Pass 2: Parameters. All units converted according to current block commands. Position /* Pass 2: Parameters. All units converted according to current block commands. Position
parameters are converted and flagged to indicate a change. These can have multiple connotations parameters are converted and flagged to indicate a change. These can have multiple connotations
for different commands. Each will be converted to their proper value upon execution. */ for different commands. Each will be converted to their proper value upon execution.
float p = 0, r = 0; NOTE: Grbl unconventionally pre-converts these parameter values based on the block G and M
commands. This is set out of the order of execution defined by NIST only for code efficiency/size
purposes, but should not affect proper g-code execution. */
float p = 0;
uint8_t l = 0; uint8_t l = 0;
char_counter = 0; char_counter = 0;
while(next_statement(&letter, &value, line, &char_counter)) { while(next_statement(&letter, &value, line, &char_counter)) {
@ -218,10 +231,10 @@ uint8_t gc_execute_line(char *line)
gc.feed_rate = to_millimeters(value); // millimeters per minute gc.feed_rate = to_millimeters(value); // millimeters per minute
} }
break; break;
case 'I': case 'J': case 'K': offset[letter-'I'] = to_millimeters(value); break; case 'I': case 'J': case 'K': gc.arc_offset[letter-'I'] = to_millimeters(value); break;
case 'L': l = trunc(value); break; case 'L': l = trunc(value); break;
case 'P': p = value; break; case 'P': p = value; break;
case 'R': r = to_millimeters(value); break; case 'R': gc.arc_radius = to_millimeters(value); break;
case 'S': case 'S':
if (value < 0) { FAIL(STATUS_INVALID_STATEMENT); } // Cannot be negative if (value < 0) { FAIL(STATUS_INVALID_STATEMENT); } // Cannot be negative
// TBD: Spindle speed not supported due to PWM issues, but may come back once resolved. // TBD: Spindle speed not supported due to PWM issues, but may come back once resolved.
@ -241,10 +254,10 @@ uint8_t gc_execute_line(char *line)
// If there were any errors parsing this line, we will return right away with the bad news // If there were any errors parsing this line, we will return right away with the bad news
if (gc.status_code) { return(gc.status_code); } if (gc.status_code) { return(gc.status_code); }
// Initialize axis index
uint8_t idx;
/* Execute Commands: Perform by order of execution defined in NIST RS274-NGC.v3, Table 8, pg.41. /* Execute Commands: Perform by order of execution defined in NIST RS274-NGC.v3, Table 8, pg.41. */
NOTE: Independent non-motion/settings parameters are set out of this order for code efficiency
and simplicity purposes, but this should not affect proper g-code execution. */
// ([F]: Set feed rate.) // ([F]: Set feed rate.)
@ -279,29 +292,29 @@ uint8_t gc_execute_line(char *line)
break; break;
case NON_MODAL_SET_COORDINATE_DATA: case NON_MODAL_SET_COORDINATE_DATA:
int_value = trunc(p); // Convert p value to int. int_value = trunc(p); // Convert p value to int.
if ((l != 2 && l != 20) || (int_value < 1 || int_value > N_COORDINATE_SYSTEM)) { // L2 and L20. P1=G54, P2=G55, ... if ((l != 2 && l != 20) || (int_value < 0 || int_value > N_COORDINATE_SYSTEM)) { // L2 and L20. P1=G54, P2=G55, ...
FAIL(STATUS_UNSUPPORTED_STATEMENT); FAIL(STATUS_UNSUPPORTED_STATEMENT);
} else if (!axis_words && l==2) { // No axis words. } else if (!axis_words && l==2) { // No axis words.
FAIL(STATUS_INVALID_STATEMENT); FAIL(STATUS_INVALID_STATEMENT);
} else { } else {
int_value--; // Adjust P index to EEPROM coordinate data indexing. if (int_value > 0) { int_value--; } // Adjust P1-P6 index to EEPROM coordinate data indexing.
if (l == 20) { else { int_value = gc.coord_select; } // Index P0 as the active coordinate system
settings_write_coord_data(int_value,gc.position);
// Update system coordinate system if currently active.
if (gc.coord_select == int_value) { memcpy(gc.coord_system,gc.position,sizeof(gc.position)); }
} else {
float coord_data[N_AXIS]; float coord_data[N_AXIS];
if (!settings_read_coord_data(int_value,coord_data)) { return(STATUS_SETTING_READ_FAIL); } if (!settings_read_coord_data(int_value,coord_data)) { return(STATUS_SETTING_READ_FAIL); }
// Update axes defined only in block. Always in machine coordinates. Can change non-active system. // Update axes defined only in block. Always in machine coordinates. Can change non-active system.
uint8_t i; for (idx=0; idx<N_AXIS; idx++) { // Axes indices are consistent, so loop may be used.
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used. if (bit_istrue(axis_words,bit(idx)) ) {
if ( bit_istrue(axis_words,bit(i)) ) { coord_data[i] = target[i]; } if (l == 20) {
coord_data[idx] = gc.position[idx]-target[idx]; // L20: Update axis current position to target
} else {
coord_data[idx] = target[idx]; // L2: Update coordinate system axis
}
}
} }
settings_write_coord_data(int_value,coord_data); settings_write_coord_data(int_value,coord_data);
// Update system coordinate system if currently active. // Update system coordinate system if currently active.
if (gc.coord_select == int_value) { memcpy(gc.coord_system,coord_data,sizeof(coord_data)); } if (gc.coord_select == int_value) { memcpy(gc.coord_system,coord_data,sizeof(coord_data)); }
} }
}
axis_words = 0; // Axis words used. Lock out from motion modes by clearing flags. axis_words = 0; // Axis words used. Lock out from motion modes by clearing flags.
break; break;
case NON_MODAL_GO_HOME_0: case NON_MODAL_GO_HOME_1: case NON_MODAL_GO_HOME_0: case NON_MODAL_GO_HOME_1:
@ -309,33 +322,36 @@ uint8_t gc_execute_line(char *line)
// and absolute and incremental modes. // and absolute and incremental modes.
if (axis_words) { if (axis_words) {
// Apply absolute mode coordinate offsets or incremental mode offsets. // Apply absolute mode coordinate offsets or incremental mode offsets.
uint8_t i; for (idx=0; idx<N_AXIS; idx++) { // Axes indices are consistent, so loop may be used.
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used. if ( bit_istrue(axis_words,bit(idx)) ) {
if ( bit_istrue(axis_words,bit(i)) ) {
if (gc.absolute_mode) { if (gc.absolute_mode) {
target[i] += gc.coord_system[i] + gc.coord_offset[i]; target[idx] += gc.coord_system[idx] + gc.coord_offset[idx];
} else { } else {
target[i] += gc.position[i]; target[idx] += gc.position[idx];
} }
} else { } else {
target[i] = gc.position[i]; target[idx] = gc.position[idx];
} }
} }
mc_line(target, -1.0, false); mc_line(target, -1.0, false);
} }
// Retreive G28/30 go-home position data (in machine coordinates) from EEPROM // Retreive G28/30 go-home position data (in machine coordinates) from EEPROM
float coord_data[N_AXIS]; float coord_data[N_AXIS];
uint8_t home_select = SETTING_INDEX_G28; if (non_modal_action == NON_MODAL_GO_HOME_0) {
if (non_modal_action == NON_MODAL_GO_HOME_1) { home_select = SETTING_INDEX_G30; } if (!settings_read_coord_data(SETTING_INDEX_G28,coord_data)) { return(STATUS_SETTING_READ_FAIL); }
if (!settings_read_coord_data(home_select,coord_data)) { return(STATUS_SETTING_READ_FAIL); } } else {
if (!settings_read_coord_data(SETTING_INDEX_G30,coord_data)) { return(STATUS_SETTING_READ_FAIL); }
}
mc_line(coord_data, -1.0, false); mc_line(coord_data, -1.0, false);
memcpy(gc.position, coord_data, sizeof(coord_data)); // gc.position[] = coord_data[]; memcpy(gc.position, coord_data, sizeof(coord_data)); // gc.position[] = coord_data[];
axis_words = 0; // Axis words used. Lock out from motion modes by clearing flags. axis_words = 0; // Axis words used. Lock out from motion modes by clearing flags.
break; break;
case NON_MODAL_SET_HOME_0: case NON_MODAL_SET_HOME_1: case NON_MODAL_SET_HOME_0: case NON_MODAL_SET_HOME_1:
home_select = SETTING_INDEX_G28; if (non_modal_action == NON_MODAL_SET_HOME_0) {
if (non_modal_action == NON_MODAL_SET_HOME_1) { home_select = SETTING_INDEX_G30; } settings_write_coord_data(SETTING_INDEX_G28,gc.position);
settings_write_coord_data(home_select,gc.position); } else {
settings_write_coord_data(SETTING_INDEX_G30,gc.position);
}
break; break;
case NON_MODAL_SET_COORDINATE_OFFSET: case NON_MODAL_SET_COORDINATE_OFFSET:
if (!axis_words) { // No axis words if (!axis_words) { // No axis words
@ -343,10 +359,9 @@ uint8_t gc_execute_line(char *line)
} else { } else {
// Update axes defined only in block. Offsets current system to defined value. Does not update when // Update axes defined only in block. Offsets current system to defined value. Does not update when
// active coordinate system is selected, but is still active unless G92.1 disables it. // active coordinate system is selected, but is still active unless G92.1 disables it.
uint8_t i; for (idx=0; idx<N_AXIS; idx++) { // Axes indices are consistent, so loop may be used.
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used. if (bit_istrue(axis_words,bit(idx)) ) {
if (bit_istrue(axis_words,bit(i)) ) { gc.coord_offset[idx] = gc.position[idx]-gc.coord_system[idx]-target[idx];
gc.coord_offset[i] = gc.position[i]-gc.coord_system[i]-target[i];
} }
} }
} }
@ -378,18 +393,17 @@ uint8_t gc_execute_line(char *line)
// Convert all target position data to machine coordinates for executing motion. Apply // Convert all target position data to machine coordinates for executing motion. Apply
// absolute mode coordinate offsets or incremental mode offsets. // absolute mode coordinate offsets or incremental mode offsets.
// NOTE: Tool offsets may be appended to these conversions when/if this feature is added. // NOTE: Tool offsets may be appended to these conversions when/if this feature is added.
uint8_t i; for (idx=0; idx<N_AXIS; idx++) { // Axes indices are consistent, so loop may be used to save flash space.
for (i=0; i<N_AXIS; i++) { // Axes indices are consistent, so loop may be used to save flash space. if ( bit_istrue(axis_words,bit(idx)) ) {
if ( bit_istrue(axis_words,bit(i)) ) {
if (!absolute_override) { // Do not update target in absolute override mode if (!absolute_override) { // Do not update target in absolute override mode
if (gc.absolute_mode) { if (gc.absolute_mode) {
target[i] += gc.coord_system[i] + gc.coord_offset[i]; // Absolute mode target[idx] += gc.coord_system[idx] + gc.coord_offset[idx]; // Absolute mode
} else { } else {
target[i] += gc.position[i]; // Incremental mode target[idx] += gc.position[idx]; // Incremental mode
} }
} }
} else { } else {
target[i] = gc.position[i]; // No axis word in block. Keep same axis position. target[idx] = gc.position[idx]; // No axis word in block. Keep same axis position.
} }
} }
@ -413,12 +427,79 @@ uint8_t gc_execute_line(char *line)
// Check if at least one of the axes of the selected plane has been specified. If in center // Check if at least one of the axes of the selected plane has been specified. If in center
// format arc mode, also check for at least one of the IJK axes of the selected plane was sent. // format arc mode, also check for at least one of the IJK axes of the selected plane was sent.
if ( !( bit_false(axis_words,bit(gc.plane_axis_2)) ) || if ( !( bit_false(axis_words,bit(gc.plane_axis_2)) ) ||
( !r && !offset[gc.plane_axis_0] && !offset[gc.plane_axis_1] ) ) { ( !gc.arc_radius && !gc.arc_offset[gc.plane_axis_0] && !gc.arc_offset[gc.plane_axis_1] ) ) {
FAIL(STATUS_INVALID_STATEMENT); FAIL(STATUS_INVALID_STATEMENT);
} else { } else {
if (r != 0) { // Arc Radius Mode if (gc.arc_radius != 0) { // Arc Radius Mode
/* // Compute arc radius and offsets
We need to calculate the center of the circle that has the designated radius and passes gc_convert_arc_radius_mode(target);
if (gc.status_code) { return(gc.status_code); }
} else { // Arc Center Format Offset Mode
gc.arc_radius = hypot(gc.arc_offset[gc.plane_axis_0], gc.arc_offset[gc.plane_axis_1]); // Compute arc radius for mc_arc
}
// Set clockwise/counter-clockwise sign for mc_arc computations
uint8_t isclockwise = false;
if (gc.motion_mode == MOTION_MODE_CW_ARC) { isclockwise = true; }
// Trace the arc
mc_arc(gc.position, target, gc.arc_offset, gc.plane_axis_0, gc.plane_axis_1, gc.plane_axis_2,
(gc.inverse_feed_rate_mode) ? inverse_feed_rate : gc.feed_rate, gc.inverse_feed_rate_mode,
gc.arc_radius, isclockwise);
}
break;
}
// Report any errors.
if (gc.status_code) { return(gc.status_code); }
// As far as the parser is concerned, the position is now == target. In reality the
// motion control system might still be processing the action and the real tool position
// in any intermediate location.
memcpy(gc.position, target, sizeof(target)); // gc.position[] = target[];
}
// 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.
sys.auto_start = false; // Disable auto cycle start. Forces pause until cycle start issued.
// If complete, reset to reload defaults (G92.2,G54,G17,G90,G94,M48,G40,M5,M9). Otherwise,
// re-enable program flow after pause complete, where cycle start will resume the program.
if (gc.program_flow == PROGRAM_FLOW_COMPLETED) { mc_reset(); }
else { gc.program_flow = PROGRAM_FLOW_RUNNING; }
}
return(gc.status_code);
}
// Parses the next statement and leaves the counter on the first character following
// the statement. Returns 1 if there was a statements, 0 if end of string was reached
// or there was an error (check state.status_code).
static uint8_t next_statement(char *letter, float *float_ptr, char *line, uint8_t *char_counter)
{
if (line[*char_counter] == 0) {
return(0); // No more statements
}
*letter = line[*char_counter];
if((*letter < 'A') || (*letter > 'Z')) {
FAIL(STATUS_EXPECTED_COMMAND_LETTER);
return(0);
}
(*char_counter)++;
if (!read_float(line, char_counter, float_ptr)) {
FAIL(STATUS_BAD_NUMBER_FORMAT);
return(0);
};
return(1);
}
static void gc_convert_arc_radius_mode(float *target)
{
/* We need to calculate the center of the circle that has the designated radius and passes
through both the current position and the target position. This method calculates the following through both the current position and the target position. This method calculates the following
set of equations where [x,y] is the vector from current to target position, d == magnitude of set of equations where [x,y] is the vector from current to target position, d == magnitude of
that vector, h == hypotenuse of the triangle formed by the radius of the circle, the distance to that vector, h == hypotenuse of the triangle formed by the radius of the circle, the distance to
@ -463,19 +544,17 @@ uint8_t gc_execute_line(char *line)
h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2) h_x2_div_d = sqrt(4 * r^2 - x^2 - y^2)/sqrt(x^2 + y^2)
i = (x - (y * h_x2_div_d))/2 i = (x - (y * h_x2_div_d))/2
j = (y + (x * h_x2_div_d))/2 j = (y + (x * h_x2_div_d))/2 */
*/
// Calculate the change in position along each selected axis // Calculate the change in position along each selected axis
float x = target[gc.plane_axis_0]-gc.position[gc.plane_axis_0]; float x = target[gc.plane_axis_0]-gc.position[gc.plane_axis_0];
float y = target[gc.plane_axis_1]-gc.position[gc.plane_axis_1]; float y = target[gc.plane_axis_1]-gc.position[gc.plane_axis_1];
clear_vector(offset); clear_vector(gc.arc_offset);
// First, use h_x2_div_d to compute 4*h^2 to check if it is negative or r is smaller // First, use h_x2_div_d to compute 4*h^2 to check if it is negative or r is smaller
// than d. If so, the sqrt of a negative number is complex and error out. // than d. If so, the sqrt of a negative number is complex and error out.
float h_x2_div_d = 4 * r*r - x*x - y*y; float h_x2_div_d = 4 * gc.arc_radius*gc.arc_radius - x*x - y*y;
if (h_x2_div_d < 0) { FAIL(STATUS_ARC_RADIUS_ERROR); return(gc.status_code); } if (h_x2_div_d < 0) { FAIL(STATUS_ARC_RADIUS_ERROR); return; }
// Finish computing h_x2_div_d. // Finish computing h_x2_div_d.
h_x2_div_d = -sqrt(h_x2_div_d)/hypot(x,y); // == -(h * 2 / d) h_x2_div_d = -sqrt(h_x2_div_d)/hypot(x,y); // == -(h * 2 / d)
// Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below)
@ -502,74 +581,13 @@ uint8_t gc_execute_line(char *line)
// even though it is advised against ever generating such circles in a single line of g-code. By // even though it is advised against ever generating such circles in a single line of g-code. By
// inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of // inverting the sign of h_x2_div_d the center of the circles is placed on the opposite side of the line of
// travel and thus we get the unadvisably long arcs as prescribed. // travel and thus we get the unadvisably long arcs as prescribed.
if (r < 0) { if (gc.arc_radius < 0) {
h_x2_div_d = -h_x2_div_d; h_x2_div_d = -h_x2_div_d;
r = -r; // Finished with r. Set to positive for mc_arc gc.arc_radius = -gc.arc_radius; // Finished with r. Set to positive for mc_arc
} }
// Complete the operation by calculating the actual center of the arc // Complete the operation by calculating the actual center of the arc
offset[gc.plane_axis_0] = 0.5*(x-(y*h_x2_div_d)); gc.arc_offset[gc.plane_axis_0] = 0.5*(x-(y*h_x2_div_d));
offset[gc.plane_axis_1] = 0.5*(y+(x*h_x2_div_d)); gc.arc_offset[gc.plane_axis_1] = 0.5*(y+(x*h_x2_div_d));
} else { // Arc Center Format Offset Mode
r = hypot(offset[gc.plane_axis_0], offset[gc.plane_axis_1]); // Compute arc radius for mc_arc
}
// Set clockwise/counter-clockwise sign for mc_arc computations
uint8_t isclockwise = false;
if (gc.motion_mode == MOTION_MODE_CW_ARC) { isclockwise = true; }
// Trace the arc
mc_arc(gc.position, target, offset, gc.plane_axis_0, gc.plane_axis_1, gc.plane_axis_2,
(gc.inverse_feed_rate_mode) ? inverse_feed_rate : gc.feed_rate, gc.inverse_feed_rate_mode,
r, isclockwise);
}
break;
}
// Report any errors.
if (gc.status_code) { return(gc.status_code); }
// As far as the parser is concerned, the position is now == target. In reality the
// motion control system might still be processing the action and the real tool position
// in any intermediate location.
memcpy(gc.position, target, sizeof(target)); // gc.position[] = target[];
}
// 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.
sys.auto_start = false; // Disable auto cycle start. Forces pause until cycle start issued.
// If complete, reset to reload defaults (G92.2,G54,G17,G90,G94,M48,G40,M5,M9). Otherwise,
// re-enable program flow after pause complete, where cycle start will resume the program.
if (gc.program_flow == PROGRAM_FLOW_COMPLETED) { mc_reset(); }
else { gc.program_flow = PROGRAM_FLOW_RUNNING; }
}
return(gc.status_code);
}
// Parses the next statement and leaves the counter on the first character following
// the statement. Returns 1 if there was a statements, 0 if end of string was reached
// or there was an error (check state.status_code).
static int next_statement(char *letter, float *float_ptr, char *line, uint8_t *char_counter)
{
if (line[*char_counter] == 0) {
return(0); // No more statements
}
*letter = line[*char_counter];
if((*letter < 'A') || (*letter > 'Z')) {
FAIL(STATUS_EXPECTED_COMMAND_LETTER);
return(0);
}
(*char_counter)++;
if (!read_float(line, char_counter, float_ptr)) {
FAIL(STATUS_BAD_NUMBER_FORMAT);
return(0);
};
return(1);
} }
/* /*

View File

@ -83,6 +83,10 @@ typedef struct {
// position in mm. Loaded from EEPROM when called. // position in mm. Loaded from EEPROM when called.
float coord_offset[N_AXIS]; // Retains the G92 coordinate offset (work coordinates) relative to float coord_offset[N_AXIS]; // Retains the G92 coordinate offset (work coordinates) relative to
// machine zero in mm. Non-persistent. Cleared upon reset and boot. // machine zero in mm. Non-persistent. Cleared upon reset and boot.
float arc_radius;
float arc_offset[N_AXIS];
} parser_state_t; } parser_state_t;
extern parser_state_t gc; extern parser_state_t gc;
@ -93,6 +97,6 @@ void gc_init();
uint8_t gc_execute_line(char *line); uint8_t gc_execute_line(char *line);
// Set g-code parser position. Input in steps. // Set g-code parser position. Input in steps.
void gc_set_current_position(int32_t x, int32_t y, int32_t z); void gc_sync_position();
#endif #endif

View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2012 Sungeun K. Jeon Copyright (c) 2012-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -59,12 +59,6 @@ void limits_init()
// your e-stop switch to the Arduino reset pin, since it is the most correct way to do this. // your e-stop switch to the Arduino reset pin, since it is the most correct way to do this.
ISR(LIMIT_INT_vect) ISR(LIMIT_INT_vect)
{ {
// TODO: This interrupt may be used to manage the homing cycle directly with the main stepper
// interrupt without adding too much to it. All it would need is some way to stop one axis
// when its limit is triggered and continue the others. This may reduce some of the code, but
// would make Grbl a little harder to read and understand down road. Holding off on this until
// we move on to new hardware or flash space becomes an issue. If it ain't broke, don't fix it.
// Ignore limit switches if already in an alarm state or in-process of executing an alarm. // 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 // 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 // moves in the planner and serial buffers are all cleared and newly sent blocks will be
@ -90,6 +84,19 @@ ISR(LIMIT_INT_vect)
// NOTE: Only the abort runtime command can interrupt this process. // NOTE: Only the abort runtime command can interrupt this process.
static void homing_cycle(uint8_t cycle_mask, int8_t pos_dir, bool invert_pin, float homing_rate) static void homing_cycle(uint8_t cycle_mask, int8_t pos_dir, bool invert_pin, float homing_rate)
{ {
/* TODO: Change homing routine to call planner instead moving at the maximum seek rates
and (max_travel+10mm?) for each axes during the search phase. The routine should monitor
the state of the limit pins and when a pin is triggered, it can disable that axes by
setting the respective step_x, step_y, or step_z value in the executing planner block.
This keeps the stepper algorithm counters from triggering the step on that particular
axis. When all axes have been triggered, we can then disable the steppers and reset
the stepper and planner buffers. This same method can be used for the locate cycles.
This will also fix the slow max feedrate of the homing 'lite' stepper algorithm.
Need to check if setting the planner steps will require them to be volatile or not. */
// Determine governing axes with finest step resolution per distance for the Bresenham // Determine governing axes with finest step resolution per distance for the Bresenham
// algorithm. This solves the issue when homing multiple axes that have different // algorithm. This solves the issue when homing multiple axes that have different
// resolutions without exceeding system acceleration setting. It doesn't have to be // resolutions without exceeding system acceleration setting. It doesn't have to be
@ -97,22 +104,17 @@ static void homing_cycle(uint8_t cycle_mask, int8_t pos_dir, bool invert_pin, fl
// and speedy homing routine. // and speedy homing routine.
// NOTE: For each axes enabled, the following calculations assume they physically move // NOTE: For each axes enabled, the following calculations assume they physically move
// an equal distance over each time step until they hit a limit switch, aka dogleg. // an equal distance over each time step until they hit a limit switch, aka dogleg.
uint32_t step_event_count = 0;
uint8_t i, dist = 0;
uint32_t steps[N_AXIS]; uint32_t steps[N_AXIS];
uint8_t dist = 0;
clear_vector(steps); clear_vector(steps);
if (cycle_mask & (1<<X_AXIS)) { for (i=0; i<N_AXIS; i++) {
if (cycle_mask & (1<<i)) {
dist++; dist++;
steps[X_AXIS] = lround(settings.steps_per_mm[X_AXIS]); steps[i] = lround(settings.steps_per_mm[i]);
step_event_count = max(step_event_count,steps[i]);
} }
if (cycle_mask & (1<<Y_AXIS)) {
dist++;
steps[Y_AXIS] = lround(settings.steps_per_mm[Y_AXIS]);
} }
if (cycle_mask & (1<<Z_AXIS)) {
dist++;
steps[Z_AXIS] = lround(settings.steps_per_mm[Z_AXIS]);
}
uint32_t step_event_count = max(steps[X_AXIS], max(steps[Y_AXIS], steps[Z_AXIS]));
// To ensure global acceleration is not exceeded, reduce the governing axes nominal rate // To ensure global acceleration is not exceeded, reduce the governing axes nominal rate
// by adjusting the actual axes distance traveled per step. This is the same procedure // by adjusting the actual axes distance traveled per step. This is the same procedure
@ -245,3 +247,32 @@ void limits_go_home()
st_go_idle(); // Call main stepper shutdown routine. st_go_idle(); // Call main stepper shutdown routine.
} }
// 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.
void limits_soft_check(float *target)
{
uint8_t idx;
for (idx=0; idx<N_AXIS; idx++) {
if (target[idx] > 0 || target[idx] < settings.max_travel[idx]) { // NOTE: max_travel is stored as negative
// Force feed hold if cycle is active. All buffered blocks are guaranteed to be within
// 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) {
protocol_execute_runtime();
if (sys.abort) { return; }
}
}
mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown.
sys.execute |= EXEC_CRIT_EVENT; // Indicate soft limit critical event
protocol_execute_runtime(); // Execute to enter critical event loop and system abort
return;
}
}
}

View File

@ -3,6 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -21,10 +22,13 @@
#ifndef limits_h #ifndef limits_h
#define limits_h #define limits_h
// initialize the limits module // Initialize the limits module
void limits_init(); void limits_init();
// perform the homing cycle // Perform the homing cycle
void limits_go_home(); void limits_go_home();
// Check for soft limit violations
void limits_soft_check(float *target);
#endif #endif

9
main.c
View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -72,7 +72,8 @@ int main(void)
// Sync cleared gcode and planner positions to current system position, which is only // Sync cleared gcode and planner positions to current system position, which is only
// cleared upon startup, not a reset/abort. // cleared upon startup, not a reset/abort.
sys_sync_current_position(); plan_sync_position();
gc_sync_position();
// Reset system variables. // Reset system variables.
sys.abort = false; sys.abort = false;
@ -101,12 +102,12 @@ int main(void)
} }
protocol_execute_runtime(); protocol_execute_runtime();
protocol_process(); // ... process the serial protocol
// When the serial protocol returns, there are no more characters in the serial read buffer to // When the serial protocol returns, there are no more characters in the serial read buffer to
// be processed and executed. This indicates that individual commands are being issued or // be processed and executed. This indicates that individual commands are being issued or
// streaming is finished. In either case, auto-cycle start, if enabled, any queued moves. // streaming is finished. In either case, auto-cycle start, if enabled, any queued moves.
if (sys.auto_start) { st_cycle_start(); } mc_auto_cycle_start();
protocol_process(); // ... process the serial protocol
} }
return 0; /* never reached */ return 0; /* never reached */

View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2011 Jens Geisler Copyright (c) 2011 Jens Geisler
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
@ -42,41 +42,15 @@
// (1 minute)/feed_rate time. // (1 minute)/feed_rate time.
// NOTE: This is the primary gateway to the grbl planner. All line motions, including arc line // NOTE: This is the primary gateway to the grbl planner. All line motions, including arc line
// segments, must pass through this routine before being passed to the planner. The seperation of // segments, must pass through this routine before being passed to the planner. The seperation of
// mc_line and plan_buffer_line is done primarily to make backlash compensation or canned cycle // mc_line and plan_buffer_line is done primarily to place non-planner-type functions from being
// integration simple and direct. // in the planner and to let backlash compensation or canned cycle integration simple and direct.
// TODO: Check for a better way to avoid having to push the arguments twice for non-backlash cases.
// However, this keeps the memory requirements lower since it doesn't have to call and hold two
// plan_buffer_lines in memory. Grbl only has to retain the original line input variables during a
// backlash segment(s).
void mc_line(float *target, float feed_rate, uint8_t invert_feed_rate) void mc_line(float *target, float feed_rate, uint8_t invert_feed_rate)
{ {
// TO TEST: Perform soft limit check here. Just check if the target x,y,z values are outside the // If enabled, check for soft limit violations. Placed here all line motions are picked up
// work envelope. Should be straightforward and efficient. By placing it here, rather than in // from everywhere in Grbl.
// the g-code parser, it directly picks up motions from everywhere in Grbl. if (bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)) { limits_soft_check(target); }
// TODO: Eventually move the soft limit check into limits.c.
if (bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)) {
uint8_t i;
for (i=0; i<N_AXIS; i++) {
// TODO: This does not account for homing switches on the other side of travel, meaning that
// the machine travel envelope is flipped or negative, instead of positive. There needs to be
// a fix to this problem before release.
if ((target[i] < 0) || (target[i] > settings.max_travel[i])) {
// TODO: Need to make this more in-line with the rest of the alarm and runtime execution handling.
// Not quite right. Also this should force Grbl to feed hold and exit, rather than stopping and alarm
// out. This would help retain machine position, but is this really required?
if (sys.state != STATE_ALARM) {
if (bit_isfalse(sys.execute,EXEC_ALARM)) {
mc_reset(); // Initiate system kill.
report_alarm_message(ALARM_SOFT_LIMIT);
sys.state = STATE_ALARM;
sys.execute |= EXEC_CRIT_EVENT; // Indicate hard limit critical event
}
}
}
}
}
// If in check gcode mode, prevent motion by blocking planner. // If in check gcode mode, prevent motion by blocking planner. Soft limits still work.
if (sys.state == STATE_CHECK_MODE) { return; } if (sys.state == STATE_CHECK_MODE) { return; }
// TODO: Backlash compensation may be installed here. Only need direction info to track when // TODO: Backlash compensation may be installed here. Only need direction info to track when
@ -85,31 +59,25 @@ void mc_line(float *target, float feed_rate, uint8_t invert_feed_rate)
// backlash steps will need to be also tracked. Not sure what the best strategy is for this, // backlash steps will need to be also tracked. Not sure what the best strategy is for this,
// i.e. keep the planner independent and do the computations in the status reporting, or let // i.e. keep the planner independent and do the computations in the status reporting, or let
// the planner handle the position corrections. The latter may get complicated. // the planner handle the position corrections. The latter may get complicated.
// TODO: Backlash comp positioning values may need to be kept at a system level, i.e. tracking
// true position after a feed hold in the middle of a backlash move. The difficulty is in making
// sure that the stepper subsystem and planner are working in sync, and the status report
// position also takes this into account.
// If the buffer is full: good! That means we are well ahead of the robot. // If the buffer is full: good! That means we are well ahead of the robot.
// Remain in this loop until there is room in the buffer. // Remain in this loop until there is room in the buffer.
do { do {
protocol_execute_runtime(); // Check for any run-time commands protocol_execute_runtime(); // Check for any run-time commands
if (sys.abort) { return; } // Bail, if system abort. if (sys.abort) { return; } // Bail, if system abort.
} while ( plan_check_full_buffer() ); if ( plan_check_full_buffer() ) { mc_auto_cycle_start(); } // Auto-cycle start when buffer is full.
plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], feed_rate, invert_feed_rate); else { break; }
} while (1);
plan_buffer_line(target, feed_rate, invert_feed_rate);
// If idle, indicate to the system there is now a planned block in the buffer ready to cycle // If idle, indicate to the system there is now a planned block in the buffer ready to cycle
// start. Otherwise ignore and continue on. // start. Otherwise ignore and continue on.
if (!sys.state) { sys.state = STATE_QUEUED; } if (!sys.state) { sys.state = STATE_QUEUED; }
// Auto-cycle start immediately after planner finishes. Enabled/disabled by grbl settings. During
// a feed hold, auto-start is disabled momentarily until the cycle is resumed by the cycle-start
// runtime command.
// NOTE: This is allows the user to decide to exclusively use the cycle start runtime command to
// begin motion or let grbl auto-start it for them. This is useful when: manually cycle-starting
// when the buffer is completely full and primed; auto-starting, if there was only one g-code
// command sent during manual operation; or if a system is prone to buffer starvation, auto-start
// helps make sure it minimizes any dwelling/motion hiccups and keeps the cycle going.
// NOTE: Moved into main loop and plan_check_full_buffer() as a test. This forces Grbl to process
// all of the commands in the serial read buffer or until the planner buffer is full before auto
// cycle starting. Will eventually need to remove the following command.
// if (sys.auto_start) { st_cycle_start(); }
} }
@ -181,8 +149,8 @@ void mc_arc(float *position, float *target, float *offset, uint8_t axis_0, uint8
This is important when there are successive arc motions. This is important when there are successive arc motions.
*/ */
// Computes: cos_T = 1 - theta_per_segment^2/2, sin_T = theta_per_segment - theta_per_segment^3/6) in ~52usec // Computes: cos_T = 1 - theta_per_segment^2/2, sin_T = theta_per_segment - theta_per_segment^3/6) in ~52usec
float cos_T = 2 - theta_per_segment*theta_per_segment; float cos_T = 2.0 - theta_per_segment*theta_per_segment;
float sin_T = theta_per_segment*0.16666667*(cos_T + 4); float sin_T = theta_per_segment*0.16666667*(cos_T + 4.0);
cos_T *= 0.5; cos_T *= 0.5;
float arc_target[N_AXIS]; float arc_target[N_AXIS];
@ -256,32 +224,40 @@ void mc_go_home()
protocol_execute_runtime(); // Check for reset and set system abort. protocol_execute_runtime(); // Check for reset and set system abort.
if (sys.abort) { return; } // Did not complete. Alarm state set by mc_alarm. if (sys.abort) { return; } // Did not complete. Alarm state set by mc_alarm.
// The machine should now be homed and machine zero has been located. Upon completion, // The machine should now be homed and machine limits have been located. By default,
// reset system position and sync internal position vectors. // grbl defines machine space as all negative, as do most CNCs. Since limit switches
clear_vector_float(sys.position); // Set machine zero // can be on either side of an axes, check and set machine zero appropriately.
sys_sync_current_position(); // At the same time, set up pull-off maneuver from axes limit switches that have been homed.
sys.state = STATE_IDLE; // Set system state to IDLE to complete motion and indicate homed.
// Pull-off axes (that have been homed) from limit switches before continuing motion.
// This provides some initial clearance off the switches and should also help prevent them // This provides some initial clearance off the switches and should also help prevent them
// from falsely tripping when hard limits are enabled. // from falsely tripping when hard limits are enabled.
float target[N_AXIS]; float pulloff_target[N_AXIS];
target[X_AXIS] = target[Y_AXIS] = target[Z_AXIS] = settings.homing_pulloff; clear_vector_float(pulloff_target); // Zero pulloff target.
if (HOMING_LOCATE_CYCLE & (1<<X_AXIS)) { clear_vector_long(sys.position); // Zero current position for now.
if (bit_isfalse(settings.homing_dir_mask,(1<<X_DIRECTION_BIT))) { target[X_AXIS] = -target[X_AXIS]; } 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;
} }
if (HOMING_LOCATE_CYCLE & (1<<Y_AXIS)) {
if (bit_isfalse(settings.homing_dir_mask,(1<<Y_DIRECTION_BIT))) { target[Y_AXIS] = -target[Y_AXIS]; }
} }
if (HOMING_LOCATE_CYCLE & (1<<Z_AXIS)) {
if (bit_isfalse(settings.homing_dir_mask,(1<<Z_DIRECTION_BIT))) { target[Z_AXIS] = -target[Z_AXIS]; }
} }
mc_line(target, settings.homing_seek_rate, false); plan_sync_position(); // Sync planner position to home for pull-off move.
sys.state = STATE_IDLE; // Set system state to IDLE to complete motion and indicate homed.
mc_line(pulloff_target, settings.homing_seek_rate, false);
st_cycle_start(); // Move it. Nothing should be in the buffer except this motion. st_cycle_start(); // Move it. Nothing should be in the buffer except this motion.
plan_synchronize(); // Make sure the motion completes. plan_synchronize(); // Make sure the motion completes.
// The gcode parser position circumvented by the pull-off maneuver, so sync position vectors. // The gcode parser position circumvented by the pull-off maneuver, so sync position now.
sys_sync_current_position(); gc_sync_position();
// If hard limits feature enabled, re-enable hard limits pin change register after homing cycle. // If hard limits feature enabled, re-enable hard limits pin change register after homing cycle.
if (bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE)) { LIMIT_PCMSK |= LIMIT_MASK; } if (bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE)) { LIMIT_PCMSK |= LIMIT_MASK; }
@ -289,6 +265,17 @@ void mc_go_home()
} }
// Auto-cycle start is a user setting that automatically begins the cycle when a user enters
// a valid motion command either manually or by a streaming tool. This is intended as a beginners
// feature to help new users to understand g-code. It can be disabled. Otherwise, the normal
// 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 // 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 // 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 // is in a motion state. If so, kills the steppers and sets the system alarm to flag position

View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -46,4 +46,7 @@ void mc_go_home();
// Performs system reset. If in motion state, kills all motion and sets system alarm. // Performs system reset. If in motion state, kills all motion and sets system alarm.
void mc_reset(); void mc_reset();
// Executes the auto cycle feature, if enabled.
void mc_auto_cycle_start();
#endif #endif

View File

@ -140,9 +140,16 @@ void delay_us(uint32_t us)
} }
} }
// Syncs all internal position vectors to the current system position.
void sys_sync_current_position() // Returns direction mask according to Grbl internal axis indexing.
uint8_t get_direction_mask(uint8_t axis_idx)
{ {
plan_set_current_position(sys.position[X_AXIS],sys.position[Y_AXIS],sys.position[Z_AXIS]); uint8_t axis_mask = 0;
gc_set_current_position(sys.position[X_AXIS],sys.position[Y_AXIS],sys.position[Z_AXIS]); switch( axis_idx ) {
case X_AXIS: axis_mask = (1<<X_DIRECTION_BIT); break;
case Y_AXIS: axis_mask = (1<<Y_DIRECTION_BIT); break;
case Z_AXIS: axis_mask = (1<<Z_DIRECTION_BIT); break;
} }
return(axis_mask);
}

View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -27,12 +27,13 @@
#include <stdbool.h> #include <stdbool.h>
#include "config.h" #include "config.h"
#include "defaults.h" #include "defaults.h"
#include "pin_map.h"
#define false 0 #define false 0
#define true 1 #define true 1
#define N_AXIS 3 // Number of axes #define N_AXIS 3 // Number of axes
#define X_AXIS 0 // Axis indexing value #define X_AXIS 0 // Axis indexing value. Must start with 0 and be continuous.
#define Y_AXIS 1 #define Y_AXIS 1
#define Z_AXIS 2 #define Z_AXIS 2
@ -44,6 +45,7 @@
// Useful macros // Useful macros
#define clear_vector(a) memset(a, 0, sizeof(a)) #define clear_vector(a) memset(a, 0, sizeof(a))
#define clear_vector_float(a) memset(a, 0.0, sizeof(float)*N_AXIS) #define clear_vector_float(a) memset(a, 0.0, sizeof(float)*N_AXIS)
#define clear_vector_long(a) memset(a, 0.0, sizeof(long)*N_AXIS)
#define max(a,b) (((a) > (b)) ? (a) : (b)) #define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b))
@ -104,7 +106,6 @@ void delay_ms(uint16_t ms);
// Delays variable-defined microseconds. Compiler compatibility fix for _delay_us(). // Delays variable-defined microseconds. Compiler compatibility fix for _delay_us().
void delay_us(uint32_t us); void delay_us(uint32_t us);
// Syncs Grbl's gcode and planner position variables with the system position. uint8_t get_direction_mask(uint8_t i);
void sys_sync_current_position();
#endif #endif

184
pin_map.h Normal file
View File

@ -0,0 +1,184 @@
/*
pin_map.h - Pin mapping configuration file
Part of Grbl
Copyright (c) 2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
/* The pin_map.h file serves as a central pin mapping settings file for different processor
types, i.e. AVR 328p or AVR Mega 2560. Grbl officially supports the Arduino Uno, but the
other supplied pin mappings are supplied by users, so your results may vary. */
#ifndef pin_map_h
#define pin_map_h
#ifdef PIN_MAP_ARDUINO_UNO // AVR 328p, Officially supported by Grbl.
// Serial port pins
#define SERIAL_RX USART_RX_vect
#define SERIAL_UDRE USART_UDRE_vect
// NOTE: All step bit and direction pins must be on the same port.
#define STEPPING_DDR DDRD
#define STEPPING_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
#define X_DIRECTION_BIT 5 // Uno Digital Pin 5
#define Y_DIRECTION_BIT 6 // Uno Digital Pin 6
#define Z_DIRECTION_BIT 7 // Uno Digital Pin 7
#define STEP_MASK ((1<<X_STEP_BIT)|(1<<Y_STEP_BIT)|(1<<Z_STEP_BIT)) // All step bits
#define DIRECTION_MASK ((1<<X_DIRECTION_BIT)|(1<<Y_DIRECTION_BIT)|(1<<Z_DIRECTION_BIT)) // All direction bits
#define STEPPING_MASK (STEP_MASK | DIRECTION_MASK) // All stepping-related bits (step/direction)
#define STEPPERS_DISABLE_DDR DDRB
#define STEPPERS_DISABLE_PORT PORTB
#define STEPPERS_DISABLE_BIT 0 // Uno Digital Pin 8
#define STEPPERS_DISABLE_MASK (1<<STEPPERS_DISABLE_BIT)
// NOTE: All limit bit pins must be on the same port
#define LIMIT_DDR DDRB
#define LIMIT_PIN PINB
#define LIMIT_PORT PORTB
#define X_LIMIT_BIT 1 // Uno Digital Pin 9
#define Y_LIMIT_BIT 2 // Uno Digital Pin 10
#define Z_LIMIT_BIT 3 // Uno Digital Pin 11
#define LIMIT_INT PCIE0 // Pin change interrupt enable pin
#define LIMIT_INT_vect PCINT0_vect
#define LIMIT_PCMSK PCMSK0 // Pin change interrupt register
#define LIMIT_MASK ((1<<X_LIMIT_BIT)|(1<<Y_LIMIT_BIT)|(1<<Z_LIMIT_BIT)) // All limit bits
#define SPINDLE_ENABLE_DDR DDRB
#define SPINDLE_ENABLE_PORT PORTB
#define SPINDLE_ENABLE_BIT 4 // Uno Digital Pin 12
#define SPINDLE_DIRECTION_DDR DDRB
#define SPINDLE_DIRECTION_PORT PORTB
#define SPINDLE_DIRECTION_BIT 5 // Uno Digital Pin 13 (NOTE: D13 can't be pulled-high input due to LED.)
#define COOLANT_FLOOD_DDR DDRC
#define COOLANT_FLOOD_PORT PORTC
#define COOLANT_FLOOD_BIT 3 // Uno Analog Pin 3
// NOTE: Uno analog pins 4 and 5 are reserved for an i2c interface, and may be installed at
// a later date if flash and memory space allows.
// #define ENABLE_M7 // Mist coolant disabled by default. Uncomment to enable.
#ifdef ENABLE_M7
#define COOLANT_MIST_DDR DDRC
#define COOLANT_MIST_PORT PORTC
#define COOLANT_MIST_BIT 4 // Uno Analog Pin 4
#endif
// NOTE: All pinouts pins must be on the same port
#define PINOUT_DDR DDRC
#define PINOUT_PIN PINC
#define PINOUT_PORT PORTC
#define PIN_RESET 0 // Uno Analog Pin 0
#define PIN_FEED_HOLD 1 // Uno Analog Pin 1
#define PIN_CYCLE_START 2 // Uno Analog Pin 2
#define PINOUT_INT PCIE1 // Pin change interrupt enable pin
#define PINOUT_INT_vect PCINT1_vect
#define PINOUT_PCMSK PCMSK1 // Pin change interrupt register
#define PINOUT_MASK ((1<<PIN_RESET)|(1<<PIN_FEED_HOLD)|(1<<PIN_CYCLE_START))
#endif
#ifdef PIN_MAP_ARDUINO_MEGA_2560 // Working @EliteEng
// Serial port pins
#define SERIAL_RX USART0_RX_vect
#define SERIAL_UDRE USART0_UDRE_vect
// Increase Buffers to make use of extra SRAM
#define RX_BUFFER_SIZE 256
#define TX_BUFFER_SIZE 128
#define BLOCK_BUFFER_SIZE 36
#define LINE_BUFFER_SIZE 100
// NOTE: All step bit and direction pins must be on the same port.
#define STEPPING_DDR DDRA
#define STEPPING_PORT PORTA
#define STEPPING_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
#define X_DIRECTION_BIT 5 // MEGA2560 Digital Pin 27
#define Y_DIRECTION_BIT 6 // MEGA2560 Digital Pin 28
#define Z_DIRECTION_BIT 7 // MEGA2560 Digital Pin 29
#define STEP_MASK ((1<<X_STEP_BIT)|(1<<Y_STEP_BIT)|(1<<Z_STEP_BIT)) // All step bits
#define DIRECTION_MASK ((1<<X_DIRECTION_BIT)|(1<<Y_DIRECTION_BIT)|(1<<Z_DIRECTION_BIT)) // All direction bits
#define STEPPING_MASK (STEP_MASK | DIRECTION_MASK) // All stepping-related bits (step/direction)
#define STEPPERS_DISABLE_DDR DDRB
#define STEPPERS_DISABLE_PORT PORTB
#define STEPPERS_DISABLE_BIT 7 // MEGA2560 Digital Pin 13
#define STEPPERS_DISABLE_MASK (1<<STEPPERS_DISABLE_BIT)
// NOTE: All limit bit pins must be on the same port
#define LIMIT_DDR DDRB
#define LIMIT_PORT PORTB
#define LIMIT_PIN PINB
#define X_LIMIT_BIT 4 // MEGA2560 Digital Pin 10
#define Y_LIMIT_BIT 5 // MEGA2560 Digital Pin 11
#define Z_LIMIT_BIT 6 // MEGA2560 Digital Pin 12
#define LIMIT_INT PCIE0 // Pin change interrupt enable pin
#define LIMIT_INT_vect PCINT0_vect
#define LIMIT_PCMSK PCMSK0 // Pin change interrupt register
#define LIMIT_MASK ((1<<X_LIMIT_BIT)|(1<<Y_LIMIT_BIT)|(1<<Z_LIMIT_BIT)) // All limit bits
#define SPINDLE_ENABLE_DDR DDRC
#define SPINDLE_ENABLE_PORT PORTC
#define SPINDLE_ENABLE_BIT 2 // MEGA2560 Digital Pin 35
#define SPINDLE_DIRECTION_DDR DDRC
#define SPINDLE_DIRECTION_PORT PORTC
#define SPINDLE_DIRECTION_BIT 1 // MEGA2560 Digital Pin 36
#define COOLANT_FLOOD_DDR DDRC
#define COOLANT_FLOOD_PORT PORTC
#define COOLANT_FLOOD_BIT 0 // MEGA2560 Digital Pin 37
// #define ENABLE_M7 // Mist coolant disabled by default. Uncomment to enable.
#ifdef ENABLE_M7
#define COOLANT_MIST_DDR DDRC
#define COOLANT_MIST_PORT PORTC
#define COOLANT_MIST_BIT 3 // MEGA2560 Digital Pin 34
#endif
// NOTE: All pinouts pins must be on the same port
#define PINOUT_DDR DDRK
#define PINOUT_PIN PINK
#define PINOUT_PORT PORTK
#define PIN_RESET 0 // MEGA2560 Analog Pin 8
#define PIN_FEED_HOLD 1 // MEGA2560 Analog Pin 9
#define PIN_CYCLE_START 2 // MEGA2560 Analog Pin 10
#define PINOUT_INT PCIE2 // Pin change interrupt enable pin
#define PINOUT_INT_vect PCINT2_vect
#define PINOUT_PCMSK PCMSK2 // Pin change interrupt register
#define PINOUT_MASK ((1<<PIN_RESET)|(1<<PIN_FEED_HOLD)|(1<<PIN_CYCLE_START))
#endif
/*
#ifdef PIN_MAP_CUSTOM_PROC
// For a custom pin map or different processor, copy and paste one of the default pin map
// settings above and modify it to your needs. Then, make sure the defined name is also
// changed in the config.h file.
#endif
*/
#endif

850
planner.c

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,8 @@
planner.h - buffers movement commands and manages the acceleration profile plan planner.h - buffers movement commands and manages the acceleration profile plan
Part of Grbl Part of Grbl
Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -21,6 +21,7 @@
#ifndef planner_h #ifndef planner_h
#define planner_h #define planner_h
#include "nuts_bolts.h"
// The number of linear motions that can be in the plan at any give time // The number of linear motions that can be in the plan at any give time
#ifndef BLOCK_BUFFER_SIZE #ifndef BLOCK_BUFFER_SIZE
@ -32,43 +33,47 @@
typedef struct { typedef struct {
// Fields used by the bresenham algorithm for tracing the line // Fields used by the bresenham algorithm for tracing the line
// NOTE: Do not change any of these values once set. The stepper algorithm uses them to execute the block correctly.
uint8_t direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h) uint8_t direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h)
uint32_t steps_x, steps_y, steps_z; // Step count along each axis int32_t steps[N_AXIS]; // Step count along each axis
int32_t step_event_count; // The number of step events required to complete this block int32_t step_event_count; // The maximum step axis count and number of steps required to complete this block.
// Fields used by the motion planner to manage acceleration // Fields used by the motion planner to manage acceleration
float nominal_speed_sqr; // The nominal speed for this block in mm/min float entry_speed_sqr; // The current planned entry speed at block junction in (mm/min)^2
float entry_speed_sqr; // Entry speed at previous-current block junction in mm/min float max_entry_speed_sqr; // Maximum allowable entry speed based on the minimum of junction limit and
float max_entry_speed_sqr; // Maximum allowable junction entry speed in mm/min // neighboring nominal speeds with overrides in (mm/min)^2
float new_entry_speed_sqr; // Temporary entry speed used by the planner float max_junction_speed_sqr; // Junction entry speed limit based on direction vectors in (mm/min)^2
float millimeters; // The total travel of this block in mm float nominal_speed_sqr; // Axis-limit adjusted nominal speed for this block in (mm/min)^2
float acceleration; float acceleration; // Axis-limit adjusted line acceleration in mm/min^2
float millimeters; // The remaining distance for this block to be executed in mm
// Settings for the trapezoid generator } plan_block_t;
uint32_t initial_rate; // The step rate at start of block
int32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
uint32_t decelerate_after; // The index of the step event on which to start decelerating
uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute
uint32_t d_next; // Scaled distance to next step
} block_t;
// Initialize the motion plan subsystem // Initialize the motion plan subsystem
void plan_init(); void plan_init();
// Add a new linear movement to the buffer. x, y and z is the signed, absolute target position in // Add a new linear movement to the buffer. target[N_AXIS] is the signed, absolute target position
// millimaters. Feed rate specifies the speed of the motion. If feed rate is inverted, the feed // 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. // rate is taken to mean "frequency" and would complete the operation in 1/feed_rate minutes.
void plan_buffer_line(float x, float y, float z, float feed_rate, uint8_t invert_feed_rate); void plan_buffer_line(float *target, float feed_rate, uint8_t invert_feed_rate);
// Called when the current block is no longer needed. Discards the block and makes the memory // Called when the current block is no longer needed. Discards the block and makes the memory
// availible for new blocks. // availible for new blocks.
void plan_discard_current_block(); void plan_discard_current_block();
// Gets the current block. Returns NULL if buffer empty // Gets the current block. Returns NULL if buffer empty
block_t *plan_get_current_block(); plan_block_t *plan_get_current_block();
uint8_t plan_next_block_index(uint8_t block_index);
plan_block_t *plan_get_block_by_index(uint8_t block_index);
float plan_calculate_velocity_profile(uint8_t block_index);
// void plan_update_partial_block(uint8_t block_index, float millimeters_remaining, uint8_t is_decelerating);
// Reset the planner position vector (in steps) // Reset the planner position vector (in steps)
void plan_set_current_position(int32_t x, int32_t y, int32_t z); void plan_sync_position();
// Reinitialize plan with a partially completed block // Reinitialize plan with a partially completed block
void plan_cycle_reinitialize(int32_t step_events_remaining); void plan_cycle_reinitialize(int32_t step_events_remaining);

476
planner_old.c Normal file
View File

@ -0,0 +1,476 @@
/*
planner.c - buffers movement commands and manages the acceleration profile plan
Part of Grbl
Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011 Jens Geisler
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
/* The ring buffer implementation gleaned from the wiring_serial library by David A. Mellis. */
#include <inttypes.h>
#include <stdlib.h>
#include "planner.h"
#include "nuts_bolts.h"
#include "stepper.h"
#include "settings.h"
#include "config.h"
#include "protocol.h"
#define SOME_LARGE_VALUE 1.0E+38 // Used by rapids and acceleration maximization calculations. Just needs
// to be larger than any feasible (mm/min)^2 or mm/sec^2 value.
static block_t block_buffer[BLOCK_BUFFER_SIZE]; // A ring buffer for motion instructions
static volatile uint8_t block_buffer_tail; // Index of the block to process now
static uint8_t block_buffer_head; // Index of the next block to be pushed
static uint8_t next_buffer_head; // Index of the next buffer head
static uint8_t block_buffer_planned; // Index of the optimally planned block
// Define planner variables
typedef struct {
int32_t position[N_AXIS]; // The planner position of the tool in absolute steps. Kept separate
// from g-code position for movements requiring multiple line motions,
// i.e. arcs, canned cycles, and backlash compensation.
float previous_unit_vec[N_AXIS]; // Unit vector of previous path line segment
float previous_nominal_speed_sqr; // Nominal speed of previous path line segment
float last_target[N_AXIS]; // Target position of previous path line segment
} planner_t;
static planner_t pl;
// Returns the index of the next block in the ring buffer
// NOTE: Removed modulo (%) operator, which uses an expensive divide and multiplication.
static uint8_t next_block_index(uint8_t block_index)
{
block_index++;
if (block_index == BLOCK_BUFFER_SIZE) { block_index = 0; }
return(block_index);
}
// Returns the index of the previous block in the ring buffer
static uint8_t prev_block_index(uint8_t block_index)
{
if (block_index == 0) { block_index = BLOCK_BUFFER_SIZE; }
block_index--;
return(block_index);
}
/* STEPPER VELOCITY PROFILE DEFINITION
less than nominal rate-> +
+--------+ <- nominal_rate /|\
/ \ / | \
initial_rate -> + \ / | + <- next->initial_rate
| + <- next->initial_rate / | |
+-------------+ initial_rate -> +----+--+
time --> ^ ^ ^ ^
| | | |
decelerate distance decelerate distance
Calculates trapezoid parameters for stepper algorithm. Each block velocity profiles can be
described as either a trapezoidal or a triangular shape. The trapezoid occurs when the block
reaches the nominal speed of the block and cruises for a period of time. A triangle occurs
when the nominal speed is not reached within the block. Some other special cases exist,
such as pure ac/de-celeration velocity profiles from beginning to end or a trapezoid that
has no deceleration period when the next block resumes acceleration.
The following function determines the type of velocity profile and stores the minimum required
information for the stepper algorithm to execute the calculated profiles. In this case, only
the new initial rate and n_steps until deceleration are computed, since the stepper algorithm
already handles acceleration and cruising and just needs to know when to start decelerating.
*/
static void calculate_trapezoid_for_block(block_t *block, float entry_speed_sqr, float exit_speed_sqr)
{
// Compute new initial rate for stepper algorithm
block->initial_rate = ceil(sqrt(entry_speed_sqr)*(INV_TIME_MULTIPLIER/(60*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
// TODO: Compute new nominal rate if a feedrate override occurs.
// block->nominal_rate = ceil(feed_rate*(RANADE_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
// Compute efficiency variable for following calculations. Removes a float divide and multiply.
// TODO: If memory allows, this can be kept in the block buffer since it doesn't change, even after feed holds.
float steps_per_mm_div_2_acc = block->step_event_count/(2*block->acceleration*block->millimeters);
// First determine intersection distance (in steps) from the exit point for a triangular profile.
// Computes: steps_intersect = steps/mm * ( distance/2 + (v_entry^2-v_exit^2)/(4*acceleration) )
int32_t intersect_distance = ceil( 0.5*(block->step_event_count + steps_per_mm_div_2_acc*(entry_speed_sqr-exit_speed_sqr)) );
// Check if this is a pure acceleration block by a intersection distance less than zero. Also
// prevents signed and unsigned integer conversion errors.
if (intersect_distance <= 0) {
block->decelerate_after = 0;
} else {
// Determine deceleration distance (in steps) from nominal speed to exit speed for a trapezoidal profile.
// Value is never negative. Nominal speed is always greater than or equal to the exit speed.
// Computes: steps_decelerate = steps/mm * ( (v_nominal^2 - v_exit^2)/(2*acceleration) )
block->decelerate_after = ceil(steps_per_mm_div_2_acc * (block->nominal_speed_sqr - exit_speed_sqr));
// The lesser of the two triangle and trapezoid distances always defines the velocity profile.
if (block->decelerate_after > intersect_distance) { block->decelerate_after = intersect_distance; }
// Finally, check if this is a pure deceleration block.
if (block->decelerate_after > block->step_event_count) { block->decelerate_after = block->step_event_count; }
}
}
/* PLANNER SPEED DEFINITION
+--------+ <- current->nominal_speed
/ \
current->entry_speed -> + \
| + <- next->entry_speed
+-------------+
time -->
Recalculates the motion plan according to the following algorithm:
1. Go over every block in reverse order and calculate a junction speed reduction (i.e. block_t.entry_speed)
so that:
a. The junction speed is equal to or less than the maximum junction speed limit
b. No speed reduction within one block requires faster deceleration than the acceleration limits.
c. The last (or newest appended) block is planned from a complete stop.
2. Go over every block in chronological (forward) order and dial down junction speed values if
a. The speed increase within one block would require faster acceleration than the acceleration limits.
When these stages are complete, all blocks have a junction entry speed that will allow all speed changes
to be performed using the overall limiting acceleration value, and where no junction speed is greater
than the max limit. In other words, it just computed the fastest possible velocity profile through all
buffered blocks, where the final buffered block is planned to come to a full stop when the buffer is fully
executed. Finally it will:
3. Convert the plan to data that the stepper algorithm needs. Only block trapezoids adjacent to a
a planner-modified junction speed with be updated, the others are assumed ok as is.
All planner computations(1)(2) are performed in floating point to minimize numerical round-off errors. Only
when planned values are converted to stepper rate parameters(3), these are integers. If another motion block
is added while executing, the planner will re-plan and update the stored optimal velocity profile as it goes.
Conceptually, the planner works like blowing up a balloon, where the balloon is the velocity profile. It's
constrained by the speeds at the beginning and end of the buffer, along with the maximum junction speeds and
nominal speeds of each block. Once a plan is computed, or balloon filled, this is the optimal velocity profile
through all of the motions in the buffer. Whenever a new block is added, this changes some of the limiting
conditions, or how the balloon is filled, so it has to be re-calculated to get the new optimal velocity profile.
Also, since the planner only computes on what's in the planner buffer, some motions with lots of short line
segments, like arcs, may seem to move slow. This is because there simply isn't enough combined distance traveled
in the entire buffer to accelerate up to the nominal speed and then decelerate to a stop at the end of the
buffer. There are a few simple solutions to this: (1) Maximize the machine acceleration. The planner will be
able to compute higher speed profiles within the same combined distance. (2) Increase line segment(s) distance.
The more combined distance the planner has to use, the faster it can go. (3) Increase the MINIMUM_PLANNER_SPEED.
Not recommended. This will change what speed the planner plans to at the end of the buffer. Can lead to lost
steps when coming to a stop. (4) [BEST] Increase the planner buffer size. The more combined distance, the
bigger the balloon, or faster it can go. But this is not possible for 328p Arduinos because its limited memory
is already maxed out. Future ARM versions should not have this issue, with look-ahead planner blocks numbering
up to a hundred or more.
NOTE: Since this function is constantly re-calculating for every new incoming block, it must be as efficient
as possible. For example, in situations like arc generation or complex curves, the short, rapid line segments
can execute faster than new blocks can be added, and the planner buffer will then starve and empty, leading
to weird hiccup-like jerky motions.
*/
static void planner_recalculate()
{
// Last/newest block in buffer. Exit speed is set with MINIMUM_PLANNER_SPEED. Always recalculated.
uint8_t block_index = block_buffer_head;
block_t *current = &block_buffer[block_index]; // Set as last/newest block in buffer
// Determine safe point for which to plan to.
uint8_t block_buffer_safe = next_block_index( block_buffer_tail );
if (block_buffer_safe == next_buffer_head) { // Only one safe block in buffer to operate on
block_buffer_planned = block_buffer_safe;
calculate_trapezoid_for_block(current, 0.0, MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED);
} else {
// TODO: need to account for the two block condition better. If the currently executing block
// is not safe, do we wait until its done? Can we treat the buffer head differently?
// Calculate trapezoid for the last/newest block.
current->entry_speed_sqr = min( current->max_entry_speed_sqr,
MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED + 2*current->acceleration*current->millimeters);
calculate_trapezoid_for_block(current, current->entry_speed_sqr, MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED);
// Reverse Pass: Back plan the deceleration curve from the last block in buffer. Cease
// planning when: (1) the last optimal planned pointer is reached. (2) the safe block
// pointer is reached, whereby the planned pointer is updated.
float entry_speed_sqr;
block_t *next;
block_index = prev_block_index(block_index);
while (block_index != block_buffer_planned) {
next = current;
current = &block_buffer[block_index];
// Exit loop and update planned pointer when the tail/safe block is reached.
if (block_index == block_buffer_safe) {
block_buffer_planned = block_buffer_safe;
break;
}
// Crudely maximize deceleration curve from the end of the non-optimally planned buffer to
// the optimal plan pointer. Forward pass will adjust and finish optimizing the plan.
if (current->entry_speed_sqr != current->max_entry_speed_sqr) {
entry_speed_sqr = next->entry_speed_sqr + 2*current->acceleration*current->millimeters;
if (entry_speed_sqr < current->max_entry_speed_sqr) {
current->entry_speed_sqr = entry_speed_sqr;
} else {
current->entry_speed_sqr = current->max_entry_speed_sqr;
}
}
block_index = prev_block_index(block_index);
}
// Forward Pass: Forward plan the acceleration curve from the planned pointer onward.
// Also scans for optimal plan breakpoints and appropriately updates the planned pointer.
block_index = block_buffer_planned; // Begin at buffer planned pointer
next = &block_buffer[prev_block_index(block_buffer_planned)]; // Set up for while loop
while (block_index != next_buffer_head) {
current = next;
next = &block_buffer[block_index];
// Any acceleration detected in the forward pass automatically moves the optimal planned
// pointer forward, since everything before this is all optimal. In other words, nothing
// can improve the plan from the buffer tail to the planned pointer by logic.
if (current->entry_speed_sqr < next->entry_speed_sqr) {
block_buffer_planned = block_index;
entry_speed_sqr = current->entry_speed_sqr + 2*current->acceleration*current->millimeters;
if (entry_speed_sqr < next->entry_speed_sqr) {
next->entry_speed_sqr = entry_speed_sqr; // Always <= max_entry_speed_sqr. Backward pass set this.
}
}
// Any block set at its maximum entry speed also creates an optimal plan up to this
// point in the buffer. The optimally planned pointer is updated.
if (next->entry_speed_sqr == next->max_entry_speed_sqr) {
block_buffer_planned = block_index;
}
// Automatically recalculate trapezoid for all buffer blocks from last plan's optimal planned
// pointer to the end of the buffer, except the last block.
calculate_trapezoid_for_block(current, current->entry_speed_sqr, next->entry_speed_sqr);
block_index = next_block_index( block_index );
}
}
}
void plan_init()
{
block_buffer_tail = block_buffer_head;
next_buffer_head = next_block_index(block_buffer_head);
block_buffer_planned = block_buffer_head;
memset(&pl, 0, sizeof(pl)); // Clear planner struct
}
inline void plan_discard_current_block()
{
if (block_buffer_head != block_buffer_tail) {
block_buffer_tail = next_block_index( block_buffer_tail );
}
}
inline block_t *plan_get_current_block()
{
if (block_buffer_head == block_buffer_tail) { return(NULL); }
return(&block_buffer[block_buffer_tail]);
}
// Returns the availability status of the block ring buffer. True, if full.
uint8_t plan_check_full_buffer()
{
if (block_buffer_tail == next_buffer_head) { return(true); }
return(false);
}
// 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()
{
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.
// All position data passed to the planner must be in terms of machine position to keep the planner
// independent of any coordinate system changes and offsets, which are handled by the g-code parser.
// NOTE: Assumes buffer is available. Buffer checks are handled at a higher level by motion_control.
// In other words, the buffer head is never equal to the buffer tail. Also the feed rate input value
// is used in three ways: as a normal feed rate if invert_feed_rate is false, as inverse time if
// invert_feed_rate is true, or as seek/rapids rate if the feed_rate value is negative (and
// invert_feed_rate always false).
void plan_buffer_line(float *target, float feed_rate, uint8_t invert_feed_rate)
{
// Prepare and initialize new block
block_t *block = &block_buffer[block_buffer_head];
block->step_event_count = 0;
block->millimeters = 0;
block->direction_bits = 0;
block->acceleration = SOME_LARGE_VALUE; // Scaled down to maximum acceleration later
// Compute and store initial move distance data.
int32_t target_steps[N_AXIS];
float unit_vec[N_AXIS], delta_mm;
uint8_t idx;
for (idx=0; idx<N_AXIS; idx++) {
// Calculate target position in absolute steps
target_steps[idx] = lround(target[idx]*settings.steps_per_mm[idx]);
// Number of steps for each axis and determine max step events
block->steps[idx] = labs(target_steps[idx]-pl.position[idx]);
block->step_event_count = max(block->step_event_count, block->steps[idx]);
// Compute individual axes distance for move and prep unit vector calculations.
delta_mm = target[idx] - pl.last_target[idx];
unit_vec[idx] = delta_mm; // Store unit vector numerator. Denominator computed later.
// Incrementally compute total move distance by Euclidean norm
block->millimeters += delta_mm*delta_mm;
// Set direction bits. Bit enabled always means direction is negative.
if (delta_mm < 0 ) { block->direction_bits |= get_direction_mask(idx); }
}
block->millimeters = sqrt(block->millimeters); // Complete millimeters calculation
// Bail if this is a zero-length block
if (block->step_event_count == 0) { return; }
// Adjust feed_rate value to mm/min depending on type of rate input (normal, inverse time, or rapids)
// TODO: Need to distinguish a rapids vs feed move for overrides. Some flag of some sort.
if (feed_rate < 0) { feed_rate = SOME_LARGE_VALUE; } // Scaled down to absolute max/rapids rate later
else if (invert_feed_rate) { feed_rate = block->millimeters/feed_rate; }
// Calculate the unit vector of the line move and the block maximum feed rate and acceleration limited
// by the maximum possible values. Block rapids rates are computed or feed rates are scaled down so
// they don't exceed the maximum axes velocities. The block acceleration is maximized based on direction
// and axes properties as well.
// NOTE: This calculation assumes all axes are orthogonal (Cartesian) and works with ABC-axes,
// if they are also orthogonal/independent. Operates on the absolute value of the unit vector.
float inverse_unit_vec_value;
float inverse_millimeters = 1.0/block->millimeters; // Inverse millimeters to remove multiple float divides
float junction_cos_theta = 0;
for (idx=0; idx<N_AXIS; idx++) {
if (unit_vec[idx] != 0) { // Avoid divide by zero.
unit_vec[idx] *= inverse_millimeters; // Complete unit vector calculation
inverse_unit_vec_value = abs(1.0/unit_vec[idx]); // Inverse to remove multiple float divides.
// Check and limit feed rate against max individual axis velocities and accelerations
feed_rate = min(feed_rate,settings.max_velocity[idx]*inverse_unit_vec_value);
block->acceleration = min(block->acceleration,settings.acceleration[idx]*inverse_unit_vec_value);
// Incrementally compute cosine of angle between previous and current path. Cos(theta) of the junction
// between the current move and the previous move is simply the dot product of the two unit vectors,
// where prev_unit_vec is negative. Used later to compute maximum junction speed.
junction_cos_theta -= pl.previous_unit_vec[idx] * unit_vec[idx];
}
}
/* Compute maximum allowable entry speed at junction by centripetal acceleration approximation.
Let a circle be tangent to both previous and current path line segments, where the junction
deviation is defined as the distance from the junction to the closest edge of the circle,
colinear with the circle center. The circular segment joining the two paths represents the
path of centripetal acceleration. Solve for max velocity based on max acceleration about the
radius of the circle, defined indirectly by junction deviation. This may be also viewed as
path width or max_jerk in the previous grbl version. This approach does not actually deviate
from path, but used as a robust way to compute cornering speeds, as it takes into account the
nonlinearities of both the junction angle and junction velocity.
NOTE: If the junction deviation value is finite, Grbl executes the motions in an exact path
mode (G61). If the junction deviation value is zero, Grbl will execute the motion in an exact
stop mode (G61.1) manner. In the future, if continuous mode (G64) is desired, the math here
is exactly the same. Instead of motioning all the way to junction point, the machine will
just follow the arc circle defined here. The Arduino doesn't have the CPU cycles to perform
a continuous mode path, but ARM-based microcontrollers most certainly do.
*/
// TODO: Acceleration need to be limited by the minimum of the two junctions.
// TODO: Need to setup a method to handle zero junction speeds when starting from rest.
if (block_buffer_head == block_buffer_tail) {
block->max_entry_speed_sqr = MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED;
} else {
// 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.
block->max_entry_speed_sqr = (block->acceleration * settings.junction_deviation * sin_theta_d2)/(1.0-sin_theta_d2);
}
// Store block nominal speed and rate
block->nominal_speed_sqr = feed_rate*feed_rate; // (mm/min)^2. Always > 0
block->nominal_rate = ceil(feed_rate*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
// Compute and store acceleration and distance traveled per step event.
block->rate_delta = ceil(block->acceleration*
((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic)
block->d_next = ceil((block->millimeters*INV_TIME_MULTIPLIER)/block->step_event_count); // (mult*mm/step)
// Update previous path unit_vector and nominal speed (squared)
memcpy(pl.previous_unit_vec, unit_vec, sizeof(unit_vec)); // pl.previous_unit_vec[] = unit_vec[]
pl.previous_nominal_speed_sqr = block->nominal_speed_sqr;
// Update planner position
memcpy(pl.position, target_steps, sizeof(target_steps)); // pl.position[] = target_steps[]
memcpy(pl.last_target, target, sizeof(target)); // pl.last_target[] = target[]
planner_recalculate();
// Update buffer head and next buffer head indices.
// NOTE: The buffer head update is atomic since it's one byte. Performed after the new plan
// calculations to help prevent overwriting scenarios with adding a new block to a low buffer.
block_buffer_head = next_buffer_head;
next_buffer_head = next_block_index(block_buffer_head);
}
// Reset the planner position vectors. Called by the system abort/initialization routine.
void plan_sync_position()
{
uint8_t idx;
for (idx=0; idx<N_AXIS; idx++) {
pl.position[idx] = sys.position[idx];
pl.last_target[idx] = sys.position[idx]/settings.steps_per_mm[idx];
}
}
// Re-initialize buffer plan with a partially completed block, assumed to exist at the buffer tail.
// Called after a steppers have come to a complete stop for a feed hold and the cycle is stopped.
void plan_cycle_reinitialize(int32_t step_events_remaining)
{
block_t *block = &block_buffer[block_buffer_tail]; // Point to partially completed block
// Only remaining millimeters and step_event_count need to be updated for planner recalculate.
// Other variables (step_x, step_y, step_z, rate_delta, etc.) all need to remain the same to
// ensure the original planned motion is resumed exactly.
block->millimeters = (block->millimeters*step_events_remaining)/block->step_event_count;
block->step_event_count = step_events_remaining;
// Re-plan from a complete stop. Reset planner entry speeds and buffer planned pointer.
block->entry_speed_sqr = 0.0;
block->max_entry_speed_sqr = MINIMUM_PLANNER_SPEED*MINIMUM_PLANNER_SPEED;
block_buffer_planned = block_buffer_tail;
planner_recalculate();
}

83
planner_old.h Normal file
View File

@ -0,0 +1,83 @@
/*
planner.h - buffers movement commands and manages the acceleration profile plan
Part of Grbl
Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef planner_h
#define planner_h
#include "nuts_bolts.h"
// The number of linear motions that can be in the plan at any give time
#ifndef BLOCK_BUFFER_SIZE
#define BLOCK_BUFFER_SIZE 17
#endif
// This struct is used when buffering the setup for each linear movement "nominal" values are as specified in
// the source g-code and may never actually be reached if acceleration management is active.
typedef struct {
// Fields used by the bresenham algorithm for tracing the line
uint8_t direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h)
uint32_t steps[N_AXIS]; // Step count along each axis
int32_t step_event_count; // The number of step events required to complete this block
// Fields used by the motion planner to manage acceleration
float nominal_speed_sqr; // Axis-limit adjusted nominal speed for this block in (mm/min)^2
float entry_speed_sqr; // Entry speed at previous-current block junction in (mm/min)^2
float max_entry_speed_sqr; // Maximum allowable junction entry speed in (mm/min)^2
float millimeters; // The total travel of this block in mm
float acceleration; // Axes-limit adjusted line acceleration in mm/min^2
// Settings for the trapezoid generator
uint32_t initial_rate; // The step rate at start of block
int32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
uint32_t decelerate_after; // The index of the step event on which to start decelerating
uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute
uint32_t d_next; // Scaled distance to next step
} block_t;
// Initialize the motion plan subsystem
void plan_init();
// 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.
void plan_buffer_line(float *target, float feed_rate, uint8_t invert_feed_rate);
// Called when the current block is no longer needed. Discards the block and makes the memory
// availible for new blocks.
void plan_discard_current_block();
// Gets the current block. Returns NULL if buffer empty
block_t *plan_get_current_block();
// Reset the planner position vector (in steps)
void plan_sync_position();
// Reinitialize plan with a partially completed block
void plan_cycle_reinitialize(int32_t step_events_remaining);
// Returns the status of the block ring buffer. True, if buffer is full.
uint8_t plan_check_full_buffer();
// Block until all buffered steps are executed
void plan_synchronize();
#endif

View File

@ -77,7 +77,7 @@ void print_uint8_base2(uint8_t n)
serial_write('0' + buf[i - 1]); serial_write('0' + buf[i - 1]);
} }
static void print_uint32_base10(unsigned long n) void print_uint32_base10(unsigned long n)
{ {
unsigned char buf[10]; unsigned char buf[10];
uint8_t i = 0; uint8_t i = 0;

View File

@ -31,6 +31,8 @@ void printPgmString(const char *s);
void printInteger(long n); void printInteger(long n);
void print_uint32_base10(uint32_t n);
void print_uint8_base2(uint8_t n); void print_uint8_base2(uint8_t n);
void printFloat(float n); void printFloat(float n);

View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -37,10 +37,16 @@ static uint8_t char_counter; // Last character counter in line variable.
static uint8_t iscomment; // Comment/block delete flag for processor to ignore comment characters. static uint8_t iscomment; // Comment/block delete flag for processor to ignore comment characters.
static void protocol_reset_line_buffer()
{
char_counter = 0;
iscomment = false;
}
void protocol_init() void protocol_init()
{ {
char_counter = 0; // Reset line input protocol_reset_line_buffer(); // Reset line input
iscomment = false;
report_init_message(); // Welcome message report_init_message(); // Welcome message
PINOUT_DDR &= ~(PINOUT_MASK); // Set as input pins PINOUT_DDR &= ~(PINOUT_MASK); // Set as input pins
@ -49,6 +55,7 @@ void protocol_init()
PCICR |= (1 << PINOUT_INT); // Enable Pin Change Interrupt PCICR |= (1 << PINOUT_INT); // Enable Pin Change Interrupt
} }
// Executes user startup script, if stored. // Executes user startup script, if stored.
void protocol_execute_startup() void protocol_execute_startup()
{ {
@ -65,6 +72,7 @@ void protocol_execute_startup()
} }
} }
// Pin change interrupt for pin-out commands, i.e. cycle start, feed hold, and reset. Sets // Pin change interrupt for pin-out commands, i.e. cycle start, feed hold, and reset. Sets
// only the runtime command execute variable to have the main program execute these when // only the runtime command execute variable to have the main program execute these when
// its ready. This works exactly like the character-based runtime commands when picked off // its ready. This works exactly like the character-based runtime commands when picked off
@ -96,6 +104,9 @@ ISR(PINOUT_INT_vect)
// limit switches, or the main program. // limit switches, or the main program.
void protocol_execute_runtime() void protocol_execute_runtime()
{ {
// Reload step segment buffer
st_prep_buffer();
if (sys.execute) { // Enter only if any bit flag is true if (sys.execute) { // Enter only if any bit flag is true
uint8_t rt_exec = sys.execute; // Avoid calling volatile multiple times uint8_t rt_exec = sys.execute; // Avoid calling volatile multiple times
@ -105,13 +116,17 @@ void protocol_execute_runtime()
if (rt_exec & (EXEC_ALARM | EXEC_CRIT_EVENT)) { if (rt_exec & (EXEC_ALARM | EXEC_CRIT_EVENT)) {
sys.state = STATE_ALARM; // Set system alarm state sys.state = STATE_ALARM; // Set system alarm state
// Critical event. Only hard/soft limit errors currently qualify.
if (rt_exec & EXEC_CRIT_EVENT) { if (rt_exec & EXEC_CRIT_EVENT) {
report_alarm_message(ALARM_LIMIT_ERROR);
report_feedback_message(MESSAGE_CRITICAL_EVENT); report_feedback_message(MESSAGE_CRITICAL_EVENT);
bit_false(sys.execute,EXEC_RESET); // Disable any existing reset bit_false(sys.execute,EXEC_RESET); // Disable any existing reset
do { do {
// Nothing. Block EVERYTHING until user issues reset or power cycles. Hard limits // Nothing. Block EVERYTHING until user issues reset or power cycles. Hard limits
// typically occur while unattended or not paying attention. Gives the user time // typically occur while unattended or not paying attention. Gives the user time
// to do what is needed before resetting, like killing the incoming stream. // 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)); } while (bit_isfalse(sys.execute,EXEC_RESET));
// Standard alarm event. Only abort during motion qualifies. // Standard alarm event. Only abort during motion qualifies.
@ -138,6 +153,8 @@ void protocol_execute_runtime()
// Initiate stepper feed hold // Initiate stepper feed hold
if (rt_exec & EXEC_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. st_feed_hold(); // Initiate feed hold.
bit_false(sys.execute,EXEC_FEED_HOLD); bit_false(sys.execute,EXEC_FEED_HOLD);
} }
@ -301,8 +318,7 @@ void protocol_process()
// Empty or comment line. Skip block. // Empty or comment line. Skip block.
report_status_message(STATUS_OK); // Send status message for syncing purposes. report_status_message(STATUS_OK); // Send status message for syncing purposes.
} }
char_counter = 0; // Reset line buffer index protocol_reset_line_buffer();
iscomment = false; // Reset comment flag
} else { } else {
if (iscomment) { if (iscomment) {
@ -320,7 +336,9 @@ void protocol_process()
// Enable comments flag and ignore all characters until ')' or EOL. // Enable comments flag and ignore all characters until ')' or EOL.
iscomment = true; iscomment = true;
} else if (char_counter >= LINE_BUFFER_SIZE-1) { } else if (char_counter >= LINE_BUFFER_SIZE-1) {
// Throw away any characters beyond the end of the line buffer // Detect line buffer overflow. Report error and reset line buffer.
report_status_message(STATUS_OVERFLOW);
protocol_reset_line_buffer();
} else if (c >= 'a' && c <= 'z') { // Upcase lowercase } else if (c >= 'a' && c <= 'z') { // Upcase lowercase
line[char_counter++] = c-'a'+'A'; line[char_counter++] = c-'a'+'A';
} else { } else {

View File

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

View File

@ -2,7 +2,7 @@
report.c - reporting and messaging methods report.c - reporting and messaging methods
Part of Grbl Part of Grbl
Copyright (c) 2012 Sungeun K. Jeon Copyright (c) 2012-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -74,6 +74,10 @@ void report_status_message(uint8_t status_code)
printPgmString(PSTR("Busy or queued")); break; printPgmString(PSTR("Busy or queued")); break;
case STATUS_ALARM_LOCK: case STATUS_ALARM_LOCK:
printPgmString(PSTR("Alarm lock")); break; printPgmString(PSTR("Alarm lock")); break;
case STATUS_SOFT_LIMIT_ERROR:
printPgmString(PSTR("Homing not enabled")); break;
case STATUS_OVERFLOW:
printPgmString(PSTR("Line overflow")); break;
} }
printPgmString(PSTR("\r\n")); printPgmString(PSTR("\r\n"));
} }
@ -84,8 +88,8 @@ void report_alarm_message(int8_t alarm_code)
{ {
printPgmString(PSTR("ALARM: ")); printPgmString(PSTR("ALARM: "));
switch (alarm_code) { switch (alarm_code) {
case ALARM_HARD_LIMIT: case ALARM_LIMIT_ERROR:
printPgmString(PSTR("Hard limit")); break; printPgmString(PSTR("Hard/soft limit")); break;
case ALARM_ABORT_CYCLE: case ALARM_ABORT_CYCLE:
printPgmString(PSTR("Abort during cycle")); break; printPgmString(PSTR("Abort during cycle")); break;
case ALARM_SOFT_LIMIT: case ALARM_SOFT_LIMIT:
@ -155,30 +159,30 @@ void report_grbl_settings() {
printPgmString(PSTR(" (z v_max, mm/min)\r\n$6=")); printFloat(settings.acceleration[X_AXIS]/(60*60)); // Convert from mm/min^2 for human readability printPgmString(PSTR(" (z v_max, mm/min)\r\n$6=")); printFloat(settings.acceleration[X_AXIS]/(60*60)); // Convert from mm/min^2 for human readability
printPgmString(PSTR(" (x accel, mm/sec^2)\r\n$7=")); printFloat(settings.acceleration[Y_AXIS]/(60*60)); // Convert from mm/min^2 for human readability printPgmString(PSTR(" (x accel, mm/sec^2)\r\n$7=")); printFloat(settings.acceleration[Y_AXIS]/(60*60)); // Convert from mm/min^2 for human readability
printPgmString(PSTR(" (y accel, mm/sec^2)\r\n$8=")); printFloat(settings.acceleration[Z_AXIS]/(60*60)); // Convert from mm/min^2 for human readability printPgmString(PSTR(" (y accel, mm/sec^2)\r\n$8=")); printFloat(settings.acceleration[Z_AXIS]/(60*60)); // Convert from mm/min^2 for human readability
printPgmString(PSTR(" (z accel, mm/sec^2)\r\n$9=")); printInteger(settings.pulse_microseconds); printPgmString(PSTR(" (z accel, mm/sec^2)\r\n$9=")); printFloat(-settings.max_travel[X_AXIS]); // Grbl internally store this as negative.
printPgmString(PSTR(" (step pulse, usec)\r\n$10=")); printFloat(settings.default_feed_rate); printPgmString(PSTR(" (x max travel, mm)\r\n$10=")); printFloat(-settings.max_travel[Y_AXIS]); // Grbl internally store this as negative.
printPgmString(PSTR(" (default feed, mm/min)\r\n$11=")); printInteger(settings.invert_mask); printPgmString(PSTR(" (y max travel, mm)\r\n$11=")); printFloat(-settings.max_travel[Z_AXIS]); // Grbl internally store this as negative.
printPgmString(PSTR(" (z max travel, mm)\r\n$12=")); printInteger(settings.pulse_microseconds);
printPgmString(PSTR(" (step pulse, usec)\r\n$13=")); printFloat(settings.default_feed_rate);
printPgmString(PSTR(" (default feed, mm/min)\r\n$14=")); printInteger(settings.invert_mask);
printPgmString(PSTR(" (step port invert mask, int:")); print_uint8_base2(settings.invert_mask); printPgmString(PSTR(" (step port invert mask, int:")); print_uint8_base2(settings.invert_mask);
printPgmString(PSTR(")\r\n$12=")); printInteger(settings.stepper_idle_lock_time); printPgmString(PSTR(")\r\n$15=")); printInteger(settings.stepper_idle_lock_time);
printPgmString(PSTR(" (step idle delay, msec)\r\n$13=")); printFloat(settings.junction_deviation); printPgmString(PSTR(" (step idle delay, msec)\r\n$16=")); printFloat(settings.junction_deviation);
printPgmString(PSTR(" (junction deviation, mm)\r\n$14=")); printFloat(settings.arc_tolerance); printPgmString(PSTR(" (junction deviation, mm)\r\n$17=")); printFloat(settings.arc_tolerance);
printPgmString(PSTR(" (arc tolerance, mm)\r\n$15=")); printInteger(settings.decimal_places); printPgmString(PSTR(" (arc tolerance, mm)\r\n$18=")); printInteger(settings.decimal_places);
printPgmString(PSTR(" (n-decimals, int)\r\n$16=")); printInteger(bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)); printPgmString(PSTR(" (n-decimals, int)\r\n$19=")); printInteger(bit_istrue(settings.flags,BITFLAG_REPORT_INCHES));
printPgmString(PSTR(" (report inches, bool)\r\n$17=")); printInteger(bit_istrue(settings.flags,BITFLAG_AUTO_START)); printPgmString(PSTR(" (report inches, bool)\r\n$20=")); printInteger(bit_istrue(settings.flags,BITFLAG_AUTO_START));
printPgmString(PSTR(" (auto start, bool)\r\n$18=")); printInteger(bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)); printPgmString(PSTR(" (auto start, bool)\r\n$21=")); printInteger(bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE));
printPgmString(PSTR(" (invert step enable, bool)\r\n$19=")); printInteger(bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE)); printPgmString(PSTR(" (invert step enable, bool)\r\n$22=")); printInteger(bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE));
printPgmString(PSTR(" (hard limits, bool)\r\n$20=")); printInteger(bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)); printPgmString(PSTR(" (soft limits, bool)\r\n$23=")); printInteger(bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE));
printPgmString(PSTR(" (homing cycle, bool)\r\n$21=")); printInteger(settings.homing_dir_mask); printPgmString(PSTR(" (hard limits, bool)\r\n$24=")); printInteger(bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE));
printPgmString(PSTR(" (homing cycle, bool)\r\n$25=")); printInteger(settings.homing_dir_mask);
printPgmString(PSTR(" (homing dir invert mask, int:")); print_uint8_base2(settings.homing_dir_mask); printPgmString(PSTR(" (homing dir invert mask, int:")); print_uint8_base2(settings.homing_dir_mask);
printPgmString(PSTR(")\r\n$22=")); printFloat(settings.homing_feed_rate); printPgmString(PSTR(")\r\n$26=")); printFloat(settings.homing_feed_rate);
printPgmString(PSTR(" (homing feed, mm/min)\r\n$23=")); printFloat(settings.homing_seek_rate); printPgmString(PSTR(" (homing feed, mm/min)\r\n$27=")); printFloat(settings.homing_seek_rate);
printPgmString(PSTR(" (homing seek, mm/min)\r\n$24=")); printInteger(settings.homing_debounce_delay); printPgmString(PSTR(" (homing seek, mm/min)\r\n$28=")); printInteger(settings.homing_debounce_delay);
printPgmString(PSTR(" (homing debounce, msec)\r\n$25=")); printFloat(settings.homing_pulloff); printPgmString(PSTR(" (homing debounce, msec)\r\n$29=")); printFloat(settings.homing_pulloff);
printPgmString(PSTR(" (homing pull-off, mm)\r\n$26=")); printFloat(settings.max_travel[X_AXIS]); printPgmString(PSTR(" (homing pull-off, mm)\r\n"));
printPgmString(PSTR(" (x travel, mm)\r\n$27=")); printFloat(settings.max_travel[Y_AXIS]);
printPgmString(PSTR(" (y travel, mm)\r\n$28=")); printFloat(settings.max_travel[Z_AXIS]);
printPgmString(PSTR(" (z travel, mm)\r\n$29=")); printInteger(bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE));
printPgmString(PSTR(" (soft limits, bool)\r\n"));
} }

View File

@ -2,7 +2,7 @@
report.h - reporting and messaging methods report.h - reporting and messaging methods
Part of Grbl Part of Grbl
Copyright (c) 2012 Sungeun K. Jeon Copyright (c) 2012-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -35,9 +35,11 @@
#define STATUS_SETTING_READ_FAIL 10 #define STATUS_SETTING_READ_FAIL 10
#define STATUS_IDLE_ERROR 11 #define STATUS_IDLE_ERROR 11
#define STATUS_ALARM_LOCK 12 #define STATUS_ALARM_LOCK 12
#define STATUS_SOFT_LIMIT_ERROR 13
#define STATUS_OVERFLOW 14
// Define Grbl alarm codes. Less than zero to distinguish alarm error from status error. // Define Grbl alarm codes. Less than zero to distinguish alarm error from status error.
#define ALARM_HARD_LIMIT -1 #define ALARM_LIMIT_ERROR -1
#define ALARM_ABORT_CYCLE -2 #define ALARM_ABORT_CYCLE -2
#define ALARM_SOFT_LIMIT -3 #define ALARM_SOFT_LIMIT -3

View File

@ -91,11 +91,7 @@ void serial_write(uint8_t data) {
} }
// Data Register Empty Interrupt handler // Data Register Empty Interrupt handler
#ifdef __AVR_ATmega644P__ ISR(SERIAL_UDRE)
ISR(USART0_UDRE_vect)
#else
ISR(USART_UDRE_vect)
#endif
{ {
// Temporary tx_buffer_tail (to optimize for volatile) // Temporary tx_buffer_tail (to optimize for volatile)
uint8_t tail = tx_buffer_tail; uint8_t tail = tx_buffer_tail;
@ -144,11 +140,7 @@ uint8_t serial_read()
} }
} }
#ifdef __AVR_ATmega644P__ ISR(SERIAL_RX)
ISR(USART0_RX_vect)
#else
ISR(USART_RX_vect)
#endif
{ {
uint8_t data = UDR0; uint8_t data = UDR0;
uint8_t next_head; uint8_t next_head;

View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -88,6 +88,7 @@ void settings_reset(bool reset_all) {
if (DEFAULT_REPORT_INCHES) { settings.flags |= BITFLAG_REPORT_INCHES; } if (DEFAULT_REPORT_INCHES) { settings.flags |= BITFLAG_REPORT_INCHES; }
if (DEFAULT_AUTO_START) { settings.flags |= BITFLAG_AUTO_START; } if (DEFAULT_AUTO_START) { settings.flags |= BITFLAG_AUTO_START; }
if (DEFAULT_INVERT_ST_ENABLE) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; } if (DEFAULT_INVERT_ST_ENABLE) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; }
if (DEFAULT_SOFT_LIMIT_ENABLE) { settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; }
if (DEFAULT_HARD_LIMIT_ENABLE) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; } if (DEFAULT_HARD_LIMIT_ENABLE) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; }
if (DEFAULT_HOMING_ENABLE) { settings.flags |= BITFLAG_HOMING_ENABLE; } if (DEFAULT_HOMING_ENABLE) { settings.flags |= BITFLAG_HOMING_ENABLE; }
settings.homing_dir_mask = DEFAULT_HOMING_DIR_MASK; settings.homing_dir_mask = DEFAULT_HOMING_DIR_MASK;
@ -97,6 +98,9 @@ void settings_reset(bool reset_all) {
settings.homing_pulloff = DEFAULT_HOMING_PULLOFF; settings.homing_pulloff = DEFAULT_HOMING_PULLOFF;
settings.stepper_idle_lock_time = DEFAULT_STEPPER_IDLE_LOCK_TIME; settings.stepper_idle_lock_time = DEFAULT_STEPPER_IDLE_LOCK_TIME;
settings.decimal_places = DEFAULT_DECIMAL_PLACES; settings.decimal_places = DEFAULT_DECIMAL_PLACES;
settings.max_travel[X_AXIS] = DEFAULT_X_MAX_TRAVEL;
settings.max_travel[Y_AXIS] = DEFAULT_Y_MAX_TRAVEL;
settings.max_travel[Z_AXIS] = DEFAULT_Z_MAX_TRAVEL;
write_global_settings(); write_global_settings();
} }
@ -165,48 +169,53 @@ uint8_t settings_store_global_setting(int parameter, float value) {
case 6: settings.acceleration[X_AXIS] = value*60*60; break; // Convert to mm/min^2 for grbl internal use. case 6: settings.acceleration[X_AXIS] = value*60*60; break; // Convert to mm/min^2 for grbl internal use.
case 7: settings.acceleration[Y_AXIS] = value*60*60; break; // Convert to mm/min^2 for grbl internal use. case 7: settings.acceleration[Y_AXIS] = value*60*60; break; // Convert to mm/min^2 for grbl internal use.
case 8: settings.acceleration[Z_AXIS] = value*60*60; break; // Convert to mm/min^2 for grbl internal use. case 8: settings.acceleration[Z_AXIS] = value*60*60; break; // Convert to mm/min^2 for grbl internal use.
case 9: case 9: settings.max_travel[X_AXIS] = -value; break; // Store as negative for grbl internal use.
case 10: settings.max_travel[Y_AXIS] = -value; break; // Store as negative for grbl internal use.
case 11: settings.max_travel[Z_AXIS] = -value; break; // Store as negative for grbl internal use.
case 12:
if (value < 3) { return(STATUS_SETTING_STEP_PULSE_MIN); } if (value < 3) { return(STATUS_SETTING_STEP_PULSE_MIN); }
settings.pulse_microseconds = round(value); break; settings.pulse_microseconds = round(value); break;
case 10: settings.default_feed_rate = value; break; case 13: settings.default_feed_rate = value; break;
case 11: settings.invert_mask = trunc(value); break; case 14: settings.invert_mask = trunc(value); break;
case 12: settings.stepper_idle_lock_time = round(value); break; case 15: settings.stepper_idle_lock_time = round(value); break;
case 13: settings.junction_deviation = fabs(value); break; case 16: settings.junction_deviation = fabs(value); break;
case 14: settings.arc_tolerance = value; break; case 17: settings.arc_tolerance = value; break;
case 15: settings.decimal_places = round(value); break; case 18: settings.decimal_places = round(value); break;
case 16: case 19:
if (value) { settings.flags |= BITFLAG_REPORT_INCHES; } if (value) { settings.flags |= BITFLAG_REPORT_INCHES; }
else { settings.flags &= ~BITFLAG_REPORT_INCHES; } else { settings.flags &= ~BITFLAG_REPORT_INCHES; }
break; break;
case 17: // Reset to ensure change. Immediate re-init may cause problems. case 20: // Reset to ensure change. Immediate re-init may cause problems.
if (value) { settings.flags |= BITFLAG_AUTO_START; } if (value) { settings.flags |= BITFLAG_AUTO_START; }
else { settings.flags &= ~BITFLAG_AUTO_START; } else { settings.flags &= ~BITFLAG_AUTO_START; }
break; break;
case 18: // Reset to ensure change. Immediate re-init may cause problems. case 21: // Reset to ensure change. Immediate re-init may cause problems.
if (value) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; } if (value) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; }
else { settings.flags &= ~BITFLAG_INVERT_ST_ENABLE; } else { settings.flags &= ~BITFLAG_INVERT_ST_ENABLE; }
break; break;
case 19: case 22:
if (value) {
if (bit_isfalse(settings.flags, BITFLAG_HOMING_ENABLE)) { return(STATUS_SOFT_LIMIT_ERROR); }
settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE;
} else { settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; }
break;
case 23:
if (value) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; } if (value) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; }
else { settings.flags &= ~BITFLAG_HARD_LIMIT_ENABLE; } else { settings.flags &= ~BITFLAG_HARD_LIMIT_ENABLE; }
limits_init(); // Re-init to immediately change. NOTE: Nice to have but could be problematic later. limits_init(); // Re-init to immediately change. NOTE: Nice to have but could be problematic later.
break; break;
case 20: case 24:
if (value) { settings.flags |= BITFLAG_HOMING_ENABLE; } if (value) { settings.flags |= BITFLAG_HOMING_ENABLE; }
else { settings.flags &= ~BITFLAG_HOMING_ENABLE; } else {
break; settings.flags &= ~BITFLAG_HOMING_ENABLE;
case 21: settings.homing_dir_mask = trunc(value); break; settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE;
case 22: settings.homing_feed_rate = value; break; }
case 23: settings.homing_seek_rate = value; break;
case 24: settings.homing_debounce_delay = round(value); break;
case 25: settings.homing_pulloff = value; break;
case 26: case 27: case 28:
if (value <= 0.0) { return(STATUS_SETTING_VALUE_NEG); }
settings.max_travel[parameter-26] = value; break;
case 29:
if (value) { settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; }
else { settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; }
break; break;
case 25: settings.homing_dir_mask = trunc(value); break;
case 26: settings.homing_feed_rate = value; break;
case 27: settings.homing_seek_rate = value; break;
case 28: settings.homing_debounce_delay = round(value); break;
case 29: settings.homing_pulloff = value; break;
default: default:
return(STATUS_INVALID_STATEMENT); return(STATUS_INVALID_STATEMENT);
} }

View File

@ -3,7 +3,7 @@
Part of Grbl Part of Grbl
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -29,15 +29,15 @@
// Version of the EEPROM data. Will be used to migrate existing data from older versions of Grbl // Version of the EEPROM data. Will be used to migrate existing data from older versions of Grbl
// when firmware is upgraded. Always stored in byte 0 of eeprom // when firmware is upgraded. Always stored in byte 0 of eeprom
#define SETTINGS_VERSION 52 #define SETTINGS_VERSION 53
// Define bit flag masks for the boolean settings in settings.flag. // Define bit flag masks for the boolean settings in settings.flag.
#define BITFLAG_REPORT_INCHES bit(0) #define BITFLAG_REPORT_INCHES bit(0)
#define BITFLAG_AUTO_START bit(1) #define BITFLAG_AUTO_START bit(1)
#define BITFLAG_INVERT_ST_ENABLE bit(2) #define BITFLAG_INVERT_ST_ENABLE bit(2)
#define BITFLAG_HARD_LIMIT_ENABLE bit(3) #define BITFLAG_HARD_LIMIT_ENABLE bit(3)
#define BITFLAG_SOFT_LIMIT_ENABLE bit(4) #define BITFLAG_HOMING_ENABLE bit(4)
#define BITFLAG_HOMING_ENABLE bit(5) #define BITFLAG_SOFT_LIMIT_ENABLE bit(5)
// Define EEPROM memory address location values for Grbl settings and parameters // Define EEPROM memory address location values for Grbl settings and parameters
// NOTE: The Atmega328p has 1KB EEPROM. The upper half is reserved for parameters and // NOTE: The Atmega328p has 1KB EEPROM. The upper half is reserved for parameters and

650
stepper.c
View File

@ -2,7 +2,7 @@
stepper.c - stepper motor driver: executes motion plans using stepper motors stepper.c - stepper motor driver: executes motion plans using stepper motors
Part of Grbl Part of Grbl
Copyright (c) 2011-2012 Sungeun K. Jeon Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2009-2011 Simen Svale Skogsrud
Grbl is free software: you can redistribute it and/or modify Grbl is free software: you can redistribute it and/or modify
@ -19,20 +19,32 @@
along with Grbl. If not, see <http://www.gnu.org/licenses/>. along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* The timer calculations of this module informed by the 'RepRap cartesian firmware' by Zack Smith
and Philipp Tiefenbacher. */
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include "stepper.h" #include "stepper.h"
#include "config.h" #include "config.h"
#include "settings.h" #include "settings.h"
#include "planner.h" #include "planner.h"
#include "nuts_bolts.h"
// Some useful constants // Some useful constants
#define TICKS_PER_MICROSECOND (F_CPU/1000000) #define TICKS_PER_MICROSECOND (F_CPU/1000000)
#define CRUISE_RAMP 0
#define ACCEL_RAMP 1 #define RAMP_NOOP_CRUISE 0
#define DECEL_RAMP 2 #define RAMP_ACCEL 1
#define RAMP_DECEL 2
#define LOAD_NOOP 0
#define LOAD_SEGMENT 1
#define LOAD_BLOCK 2
#define SEGMENT_NOOP 0
#define SEGMENT_END_OF_BLOCK bit(0)
#define RAMP_CHANGE_ACCEL bit(1)
#define RAMP_CHANGE_DECEL bit(2)
#define MINIMUM_STEPS_PER_SEGMENT 1 // Don't change
#define SEGMENT_BUFFER_SIZE 6
// Stepper state variable. Contains running data and trapezoid variables. // Stepper state variable. Contains running data and trapezoid variables.
typedef struct { typedef struct {
@ -40,44 +52,85 @@ typedef struct {
int32_t counter_x, // Counter variables for the bresenham line tracer int32_t counter_x, // Counter variables for the bresenham line tracer
counter_y, counter_y,
counter_z; counter_z;
uint32_t event_count; // Total event count. Retained for feed holds. uint8_t segment_steps_remaining; // Steps remaining in line segment motion
uint32_t step_events_remaining; // Steps remaining in motion
// Used by Pramod Ranade inverse time algorithm // Used by inverse time algorithm to track step rate
int32_t delta_d; // Ranade distance traveled per interrupt tick int32_t counter_dist; // Inverse time distance traveled since last step event
int32_t d_counter; // Ranade distance traveled since last step event uint32_t ramp_rate; // Inverse time distance traveled per interrupt tick
uint8_t ramp_count; // Acceleration interrupt tick counter. uint32_t dist_per_tick;
uint8_t ramp_type; // Ramp type variable.
uint8_t execute_step; // Flags step execution for each interrupt.
} stepper_t;
static stepper_t st;
static block_t *current_block; // A pointer to the block currently being traced
// Used by the stepper driver interrupt // Used by the stepper driver interrupt
static uint8_t step_pulse_time; // Step pulse reset time after step rise uint8_t execute_step; // Flags step execution for each interrupt.
static uint8_t out_bits; // The next stepping-bits to be output uint8_t step_pulse_time; // Step pulse reset time after step rise
uint8_t out_bits; // The next stepping-bits to be output
uint8_t load_flag;
// NOTE: If the main interrupt is guaranteed to be complete before the next interrupt, then uint8_t counter_ramp;
// this blocking variable is no longer needed. Only here for safety reasons. uint8_t ramp_type;
static volatile uint8_t busy; // True when "Stepper Driver Interrupt" is being serviced. Used to avoid retriggering that handler. } stepper_t;
static stepper_t st;
// Stores stepper common data for executing steps in the segment buffer. Data can change mid-block when the
// planner updates the remaining block velocity profile with a more optimal plan or a feedrate override occurs.
// NOTE: Normally, this buffer is partially in-use, but, for the worst case scenario, it will never exceed
// the number of accessible stepper buffer segments (SEGMENT_BUFFER_SIZE-1).
typedef struct {
int32_t step_events_remaining; // Tracks step event count for the executing planner block
uint32_t dist_per_step; // Scaled distance to next step
uint32_t initial_rate; // Initialized step rate at re/start of a planner block
uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute
uint32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
uint32_t current_approx_rate; // Tracks the approximate segment rate to predict steps per segment to execute
int32_t decelerate_after; // Tracks when to initiate deceleration according to the planner block
float mm_per_step;
} st_data_t;
static st_data_t segment_data[SEGMENT_BUFFER_SIZE-1];
// Primary stepper segment ring buffer. Contains small, short line segments for the stepper algorithm to execute,
// which are "checked-out" incrementally from the first block in the planner buffer. Once "checked-out", the steps
// in the segments buffer cannot be modified by the planner, where the remaining planner block steps still can.
typedef struct {
uint8_t n_step; // Number of step events to be executed for this segment
uint8_t st_data_index; // Stepper buffer common data index. Uses this information to execute this segment.
uint8_t flag; // Stepper algorithm bit-flag for special execution conditions.
} st_segment_t;
static st_segment_t segment_buffer[SEGMENT_BUFFER_SIZE];
// Step segment ring buffer indices
static volatile uint8_t segment_buffer_tail;
static volatile uint8_t segment_buffer_head;
static uint8_t segment_next_head;
static volatile uint8_t busy; // Used to avoid ISR nesting of the "Stepper Driver Interrupt". Should never occur though.
static plan_block_t *pl_current_block; // A pointer to the planner block currently being traced
static st_segment_t *st_current_segment;
static st_data_t *st_current_data;
// Pointers for the step segment being prepped from the planner buffer. Accessed only by the
// main program. Pointers may be planning segments or planner blocks ahead of what being executed.
static plan_block_t *pl_prep_block; // Pointer to the planner block being prepped
static st_data_t *st_prep_data; // Pointer to the stepper common data being prepped
static uint8_t pl_prep_index; // Index of planner block being prepped
static uint8_t st_data_prep_index; // Index of stepper common data block being prepped
static uint8_t pl_partial_block_flag; // Flag indicating the planner has modified the prepped planner block
// __________________________ /* __________________________
// /| |\ _________________ ^ /| |\ _________________ ^
// / | | \ /| |\ | / | | \ /| |\ |
// / | | \ / | | \ s / | | \ / | | \ s
// / | | | | | \ p / | | | | | \ p
// / | | | | | \ e / | | | | | \ e
// +-----+------------------------+---+--+---------------+----+ e +-----+------------------------+---+--+---------------+----+ e
// | BLOCK 1 | BLOCK 2 | d | BLOCK 1 | BLOCK 2 | d
//
// time -----> time ----->
//
// The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates by block->rate_delta The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates by block->rate_delta
// until reaching cruising speed block->nominal_rate, and/or until step_events_remaining reaches block->decelerate_after until reaching cruising speed block->nominal_rate, and/or until step_events_remaining reaches block->decelerate_after
// after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as
// +/- block->rate_delta velocity increments by the midpoint rule at each ACCELERATION_TICKS_PER_SECOND. +/- block->rate_delta velocity increments by the midpoint rule at each ACCELERATION_TICKS_PER_SECOND.
*/
// Stepper state initialization. Cycle should only start if the st.cycle_start flag is // Stepper state initialization. Cycle should only start if the st.cycle_start flag is
// enabled. Startup init and limits call this function but shouldn't start the cycle. // enabled. Startup init and limits call this function but shouldn't start the cycle.
@ -91,23 +144,27 @@ void st_wake_up()
} }
if (sys.state == STATE_CYCLE) { if (sys.state == STATE_CYCLE) {
// Initialize stepper output bits // Initialize stepper output bits
out_bits = settings.invert_mask; st.out_bits = settings.invert_mask;
// Initialize step pulse timing from settings. // Initialize step pulse timing from settings.
step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3); st.step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3);
// Enable stepper driver interrupt // Enable stepper driver interrupt
st.execute_step = false; st.execute_step = false;
st.load_flag = LOAD_BLOCK;
TCNT2 = 0; // Clear Timer2 TCNT2 = 0; // Clear Timer2
TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt
TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler
} }
} }
// Stepper shutdown // Stepper shutdown
void st_go_idle() void st_go_idle()
{ {
// Disable stepper driver interrupt. Allow Timer0 to finish. It will disable itself. // Disable stepper driver interrupt. Allow Timer0 to finish. It will disable itself.
TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt
TCCR2B = 0; // Disable Timer2 TCCR2B = 0; // Disable Timer2
busy = false;
// Disable steppers only upon system alarm activated or by user setting to not be kept enabled. // Disable steppers only upon system alarm activated or by user setting to not be kept enabled.
if ((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) { if ((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) {
@ -124,22 +181,36 @@ void st_go_idle()
} }
// "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based /* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based
// on the Pramod Ranade inverse time stepper algorithm, where a timer ticks at a constant on an inverse time stepper algorithm, where a timer ticks at a constant frequency and uses
// frequency and uses time-distance counters to track when its the approximate time for any time-distance counters to track when its the approximate time for a step event. For reference,
// step event. However, the Ranade algorithm, as described, is susceptible to numerical round-off, a similar inverse-time algorithm by Pramod Ranade is susceptible to numerical round-off, as
// meaning that some axes steps may not execute for a given multi-axis motion. described, meaning that some axes steps may not execute correctly for a given multi-axis motion.
// Grbl's algorithm slightly differs by using a single Ranade time-distance counter to manage Grbl's algorithm differs by using a single inverse time-distance counter to manage a
// a Bresenham line algorithm for multi-axis step events which ensures the number of steps for Bresenham line algorithm for multi-axis step events, which ensures the number of steps for
// each axis are executed exactly. In other words, it uses a Bresenham within a Bresenham algorithm, each axis are executed exactly. In other words, Grbl uses a Bresenham within a Bresenham
// where one tracks time(Ranade) and the other steps. algorithm, where one tracks time for step events and the other steps for multi-axis moves.
// This interrupt pops blocks from the block_buffer and executes them by pulsing the stepper pins Grbl specifically uses the Bresenham algorithm due to its innate mathematical exactness and
// appropriately. It is supported by The Stepper Port Reset Interrupt which it uses to reset the low computational overhead, requiring simple integer +,- counters only.
// stepper port after each pulse. The bresenham line tracer algorithm controls all three stepper This interrupt pops blocks from the step segment buffer and executes them by pulsing the
// outputs simultaneously with these two interrupts. stepper pins appropriately. It is supported by The Stepper Port Reset Interrupt which it uses
// to reset the stepper port after each pulse. The bresenham line tracer algorithm controls all
// NOTE: Average time in this ISR is: 5 usec iterating timers only, 20-25 usec with step event, or three stepper outputs simultaneously with these two interrupts.
// 15 usec when popping a block. So, ensure Ranade frequency and step pulse times work with this. */
/* TODO:
- Measure time in ISR. Typical and worst-case. Should be virtually identical to last algorithm.
There are no major changes to the base operations of this ISR with the new segment buffer.
- Write how the acceleration counters work and why they are set at half via mid-point rule.
- Determine if placing the position counters elsewhere (or change them to 8-bit variables that
are added to the system position counters at the end of a segment) frees up cycles.
- Write a blurb about how the acceleration should be handled within the ISR. All of the
time/step/ramp counters accurately keep track of the remainders and phasing of the variables
with time. This means we do not have to compute them via expensive floating point beforehand.
- Need to do an analysis to determine if these counters are really that much cheaper. At least
find out when it isn't anymore. Particularly when the ISR is at a very high frequency.
- Create NOTE: to describe that the total time in this ISR must be less than the ISR frequency
in its worst case scenario.
*/
ISR(TIMER2_COMPA_vect) ISR(TIMER2_COMPA_vect)
{ {
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR // SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
@ -149,159 +220,159 @@ ISR(TIMER2_COMPA_vect)
// before any step pulse due to algorithm design. // before any step pulse due to algorithm design.
if (st.execute_step) { if (st.execute_step) {
st.execute_step = false; st.execute_step = false;
STEPPING_PORT = ( STEPPING_PORT & ~(DIRECTION_MASK | STEP_MASK) ) | out_bits; STEPPING_PORT = ( STEPPING_PORT & ~(DIRECTION_MASK | STEP_MASK) ) | st.out_bits;
TCNT0 = step_pulse_time; // Reload Timer0 counter. TCNT0 = st.step_pulse_time; // Reload Timer0 counter.
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
} }
busy = true; busy = true;
sei(); // Re-enable interrupts. This ISR will still finish before returning to main program. sei(); // Re-enable interrupts to allow Stepper Port Reset Interrupt to fire on-time.
// NOTE: The remaining code in this ISR will finish before returning to main program.
// If there is no current block, attempt to pop one from the buffer // If there is no step segment, attempt to pop one from the stepper buffer
if (current_block == NULL) { if (st.load_flag != LOAD_NOOP) {
// Anything in the buffer? If so, initialize next motion. // Anything in the buffer? If so, load and initialize next step segment.
current_block = plan_get_current_block(); if (segment_buffer_head != segment_buffer_tail) {
if (current_block != NULL) {
// By algorithm design, the loading of the next block never coincides with a step event,
// since there is always one Ranade timer tick before a step event occurs. This means
// that the Bresenham counter math never is performed at the same time as the loading
// of a block, hence helping minimize total time spent in this interrupt.
// Initialize direction bits for block // Initialize new step segment and load number of steps to execute
out_bits = current_block->direction_bits ^ settings.invert_mask; st_current_segment = &segment_buffer[segment_buffer_tail];
st.execute_step = true; // Set flag to set direction bits. st.segment_steps_remaining = st_current_segment->n_step;
// Initialize Bresenham variables // If the new segment starts a new planner block, initialize stepper variables and counters.
st.counter_x = (current_block->step_event_count >> 1); // NOTE: For new segments only, the step counters are not updated to ensure step phasing is continuous.
if (st.load_flag == LOAD_BLOCK) {
pl_current_block = plan_get_current_block(); // Should always be there. Stepper buffer handles this.
st_current_data = &segment_data[segment_buffer[segment_buffer_tail].st_data_index];
// Initialize direction bits for block. Set execute flag to set directions bits upon next ISR tick.
st.out_bits = pl_current_block->direction_bits ^ settings.invert_mask;
st.execute_step = true;
// Initialize Bresenham line counters
st.counter_x = (pl_current_block->step_event_count >> 1);
st.counter_y = st.counter_x; st.counter_y = st.counter_x;
st.counter_z = st.counter_x; st.counter_z = st.counter_x;
st.event_count = current_block->step_event_count;
st.step_events_remaining = st.event_count;
// During feed hold, do not update Ranade counter, rate, or ramp type. Keep decelerating. // Initialize inverse time, step rate data, and acceleration ramp counters
if (sys.state == STATE_CYCLE) { st.counter_dist = st_current_data->dist_per_step; // dist_per_step always greater than ramp_rate.
// Initialize Ranade variables st.ramp_rate = st_current_data->initial_rate;
st.d_counter = current_block->d_next; st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK/2; // Initialize ramp counter via midpoint rule
st.delta_d = current_block->initial_rate; st.ramp_type = RAMP_NOOP_CRUISE; // Initialize as no ramp operation. Corrected later if necessary.
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
// Initialize ramp type. // Ensure the initial step rate exceeds the MINIMUM_STEP_RATE.
if (st.step_events_remaining == current_block->decelerate_after) { st.ramp_type = DECEL_RAMP; } if (st.ramp_rate < MINIMUM_STEP_RATE) { st.dist_per_tick = MINIMUM_STEP_RATE; }
else if (st.delta_d == current_block->nominal_rate) { st.ramp_type = CRUISE_RAMP; } else { st.dist_per_tick = st.ramp_rate; }
else { st.ramp_type = ACCEL_RAMP; }
} }
// Check if ramp conditions have changed. If so, update ramp counters and control variables.
if ( st_current_segment->flag & (RAMP_CHANGE_DECEL | RAMP_CHANGE_ACCEL) ) {
/* Compute correct ramp count for a ramp change. Upon a switch from acceleration to deceleration,
or vice-versa, the new ramp count must be set to trigger the next acceleration tick equal to
the number of ramp ISR ticks counted since the last acceleration tick. This is ensures the
ramp is executed exactly as the plan dictates. Otherwise, when a ramp begins from a known
rate (nominal/cruise or initial), the ramp count must be set to ISR_TICKS_PER_ACCELERATION_TICK/2
as mandated by the mid-point rule. For the latter conditions, the ramp count have been
initialized such that the following computation is still correct. */
st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK-st.counter_ramp;
if ( st_current_segment->flag & RAMP_CHANGE_DECEL ) { st.ramp_type = RAMP_DECEL; }
else { st.ramp_type = RAMP_ACCEL; }
}
st.load_flag = LOAD_NOOP; // Segment motion loaded. Set no-operation flag to skip during execution.
} else { } else {
// Can't discard planner block here if a feed hold stops in middle of block.
st_go_idle(); st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end
busy = false;
return; // Nothing to do but exit. return; // Nothing to do but exit.
} }
} }
// Adjust inverse time counter for ac/de-celerations // Adjust inverse time counter for ac/de-celerations
if (st.ramp_type) { if (st.ramp_type) { // Ignored when ramp type is RAMP_NOOP_CRUISE
// Tick acceleration ramp counter st.counter_ramp--; // Tick acceleration ramp counter
st.ramp_count--; if (st.counter_ramp == 0) { // Adjust step rate when its time
if (st.ramp_count == 0) { st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter if (st.ramp_type == RAMP_ACCEL) { // Adjust velocity for acceleration
if (st.ramp_type == ACCEL_RAMP) { // Adjust velocity for acceleration st.ramp_rate += st_current_data->rate_delta;
st.delta_d += current_block->rate_delta; if (st.ramp_rate >= st_current_data->nominal_rate) { // Reached nominal rate.
if (st.delta_d >= current_block->nominal_rate) { // Reached cruise state. st.ramp_rate = st_current_data->nominal_rate; // Set cruising velocity
st.ramp_type = CRUISE_RAMP; st.ramp_type = RAMP_NOOP_CRUISE; // Set ramp flag to cruising
st.delta_d = current_block->nominal_rate; // Set cruise velocity st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK/2; // Re-initialize counter for next ramp change.
} }
} else if (st.ramp_type == DECEL_RAMP) { // Adjust velocity for deceleration } else { // Adjust velocity for deceleration.
if (st.delta_d > current_block->rate_delta) { if (st.ramp_rate > st_current_data->rate_delta) {
st.delta_d -= current_block->rate_delta; st.ramp_rate -= st_current_data->rate_delta;
} else { } else { // Moving near zero feed rate. Gracefully slow down.
st.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow. st.ramp_rate >>= 1; // Integer divide by 2 until complete. Also prevents overflow.
} }
} }
// Adjust for minimum step rate, but retain operating ramp rate for accurate velocity tracing.
if (st.ramp_rate < MINIMUM_STEP_RATE) { st.dist_per_tick = MINIMUM_STEP_RATE; }
else { st.dist_per_tick = st.ramp_rate; }
} }
} }
// Iterate Pramod Ranade inverse time counter. Triggers each Bresenham step event. // Iterate inverse time counter. Triggers each Bresenham step event.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_counter -= MINIMUM_STEP_RATE; } st.counter_dist -= st.dist_per_tick;
else { st.d_counter -= st.delta_d; }
// Execute Bresenham step event, when it's time to do so. // Execute Bresenham step event, when it's time to do so.
if (st.d_counter < 0) { if (st.counter_dist < 0) {
st.d_counter += current_block->d_next; st.counter_dist += st_current_data->dist_per_step; // Reload inverse time counter
// Check for feed hold state and execute accordingly. st.out_bits = pl_current_block->direction_bits; // Reset out_bits and reload direction bits
if (sys.state == STATE_HOLD) {
if (st.ramp_type != DECEL_RAMP) {
st.ramp_type = DECEL_RAMP;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
}
if (st.delta_d <= current_block->rate_delta) {
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP);
busy = false;
return;
}
}
// TODO: Vary Bresenham resolution for smoother motions or enable faster step rates (>20kHz).
out_bits = current_block->direction_bits; // Reset out_bits and reload direction bits
st.execute_step = true; st.execute_step = true;
// Execute step displacement profile by Bresenham line algorithm // Execute step displacement profile by Bresenham line algorithm
st.counter_x -= current_block->steps_x; st.counter_x -= pl_current_block->steps[X_AXIS];
if (st.counter_x < 0) { if (st.counter_x < 0) {
out_bits |= (1<<X_STEP_BIT); st.out_bits |= (1<<X_STEP_BIT);
st.counter_x += st.event_count; st.counter_x += pl_current_block->step_event_count;
if (out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; } if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; } else { sys.position[X_AXIS]++; }
} }
st.counter_y -= current_block->steps_y; st.counter_y -= pl_current_block->steps[Y_AXIS];
if (st.counter_y < 0) { if (st.counter_y < 0) {
out_bits |= (1<<Y_STEP_BIT); st.out_bits |= (1<<Y_STEP_BIT);
st.counter_y += st.event_count; st.counter_y += pl_current_block->step_event_count;
if (out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; } if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; } else { sys.position[Y_AXIS]++; }
} }
st.counter_z -= current_block->steps_z; st.counter_z -= pl_current_block->steps[Z_AXIS];
if (st.counter_z < 0) { if (st.counter_z < 0) {
out_bits |= (1<<Z_STEP_BIT); st.out_bits |= (1<<Z_STEP_BIT);
st.counter_z += st.event_count; st.counter_z += pl_current_block->step_event_count;
if (out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; } if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; } else { sys.position[Z_AXIS]++; }
} }
// Check step events for trapezoid change or end of block. // Check step events for trapezoid change or end of block.
st.step_events_remaining--; // Decrement step events count st.segment_steps_remaining--; // Decrement step events count
if (st.step_events_remaining) { if (st.segment_steps_remaining == 0) {
if (st.ramp_type != DECEL_RAMP) { // Line move is complete, set load line flag to check for new move.
// Acceleration and cruise handled by ramping. Just check for deceleration. // Check if last line move in planner block. Discard if so.
if (st.step_events_remaining <= current_block->decelerate_after) { if (st_current_segment->flag & SEGMENT_END_OF_BLOCK) {
st.ramp_type = DECEL_RAMP;
if (st.step_events_remaining == current_block->decelerate_after) {
if (st.delta_d == current_block->nominal_rate) {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Set ramp counter for trapezoid
} else {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK-st.ramp_count; // Set ramp counter for triangle
}
}
}
}
} else {
// If current block is finished, reset pointer
current_block = NULL;
plan_discard_current_block(); plan_discard_current_block();
st.load_flag = LOAD_BLOCK;
} else {
st.load_flag = LOAD_SEGMENT;
} }
out_bits ^= settings.invert_mask; // Apply step port invert mask // Discard current segment by advancing buffer tail index
if ( ++segment_buffer_tail == SEGMENT_BUFFER_SIZE) { segment_buffer_tail = 0; }
}
st.out_bits ^= settings.invert_mask; // Apply step port invert mask
} }
busy = false; busy = false;
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT;
} }
// The Stepper Port Reset Interrupt: Timer0 OVF interrupt handles the falling edge of the
// step pulse. This should always trigger before the next Timer2 COMPA interrupt and independently // 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. // finish, if Timer2 is disabled after completing a move.
ISR(TIMER0_OVF_vect) ISR(TIMER0_OVF_vect)
{ {
@ -314,10 +385,23 @@ ISR(TIMER0_OVF_vect)
void st_reset() void st_reset()
{ {
memset(&st, 0, sizeof(st)); memset(&st, 0, sizeof(st));
current_block = NULL;
st.load_flag = LOAD_BLOCK;
busy = false; busy = false;
pl_current_block = NULL; // Planner block pointer used by stepper algorithm
pl_prep_block = NULL; // Planner block pointer used by segment buffer
pl_prep_index = 0; // Planner buffer indices are also reset to zero.
st_data_prep_index = 0;
segment_buffer_tail = 0;
segment_buffer_head = 0; // empty = tail
segment_next_head = 1;
pl_partial_block_flag = false;
} }
// Initialize and start the stepper motor subsystem // Initialize and start the stepper motor subsystem
void st_init() void st_init()
{ {
@ -351,10 +435,12 @@ void st_cycle_start()
{ {
if (sys.state == STATE_QUEUED) { if (sys.state == STATE_QUEUED) {
sys.state = STATE_CYCLE; sys.state = STATE_CYCLE;
st_prep_buffer(); // Initialize step segment buffer before beginning cycle.
st_wake_up(); st_wake_up();
} }
} }
// Execute a feed hold with deceleration, only during cycle. Called by main program. // Execute a feed hold with deceleration, only during cycle. Called by main program.
void st_feed_hold() void st_feed_hold()
{ {
@ -364,6 +450,7 @@ void st_feed_hold()
} }
} }
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by // Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
// runtime command execution in the main program, ensuring that the planner re-plans safely. // runtime command execution in the main program, ensuring that the planner re-plans safely.
// NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper // NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
@ -371,16 +458,249 @@ void st_feed_hold()
// Only the planner de/ac-celerations profiles and stepper rates have been updated. // Only the planner de/ac-celerations profiles and stepper rates have been updated.
void st_cycle_reinitialize() void st_cycle_reinitialize()
{ {
if (current_block != NULL) { // if (pl_current_block != NULL) {
// Replan buffer from the feed hold stop location. // Replan buffer from the feed hold stop location.
plan_cycle_reinitialize(st.step_events_remaining);
st.ramp_type = ACCEL_RAMP; // TODO: Need to add up all of the step events in the current planner block to give
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // back to the planner. Should only need it for the current block.
st.delta_d = 0; // BUT! The planner block millimeters is all changed and may be changed into the next
sys.state = STATE_QUEUED; // planner block. The block millimeters would need to be recalculated via step counts
} else { // and the mm/step variable.
// OR. Do we plan the feed hold itself down with the planner.
// plan_cycle_reinitialize(st_current_data->step_events_remaining);
// st.ramp_type = RAMP_ACCEL;
// st.counter_ramp = ISR_TICKS_PER_ACCELERATION_TICK/2;
// st.ramp_rate = 0;
// sys.state = STATE_QUEUED;
// } else {
// sys.state = STATE_IDLE;
// }
sys.state = STATE_IDLE; sys.state = STATE_IDLE;
} }
/* Prepares step segment buffer. Continuously called from main program.
The segment buffer is an intermediary buffer interface between the execution of steps
by the stepper algorithm and the velocity profiles generated by the planner. The stepper
algorithm only executes steps within the segment buffer and is filled by the main program
when steps are "checked-out" from the first block in the planner buffer. This keeps the
step execution and planning optimization processes atomic and protected from each other.
The number of steps "checked-out" from the planner buffer and the number of segments in
the segment buffer is sized and computed such that no operation in the main program takes
longer than the time it takes the stepper algorithm to empty it before refilling it.
Currently, the segment buffer conservatively holds roughly up to 40-60 msec of steps.
NOTE: The segment buffer executes a set number of steps over an approximate time period.
If we try to execute over a fixed time period, it is difficult to guarantee or predict
how many steps will execute over it, especially when the step pulse phasing between the
neighboring segments must also be kept consistent. Meaning that, if the last segment step
pulses right before a segment end, the next segment must delay its first pulse so that the
step pulses are consistently spaced apart over time to keep the step pulse train nice and
smooth. Keeping track of phasing and ensuring that the exact number of steps are executed
as defined by the planner block, the related computational overhead can get quickly and
prohibitively expensive, especially in real-time.
Since the stepper algorithm automatically takes care of the step pulse phasing with
its ramp and inverse time counters by retaining the count remainders, we don't have to
explicitly and expensively track and synchronize the exact number of steps, time, and
phasing of steps. All we need to do is approximate the number of steps in each segment
such that the segment buffer has enough execution time for the main program to do what
it needs to do and refill it when it comes back. In other words, we just need to compute
a cheap approximation of the current velocity and the number of steps over it.
*/
/*
TODO: Figure out how to enforce a deceleration when a feedrate override is reduced.
The problem is that when an override is reduced, the planner may not plan back to
the current rate. Meaning that the velocity profiles for certain conditions no longer
are trapezoidal or triangular. For example, if the current block is cruising at a
nominal rate and the feedrate override is reduced, the new nominal rate will now be
lower. The velocity profile must first decelerate to the new nominal rate and then
follow on the new plan. So the remaining velocity profile will have a decelerate,
cruise, and another decelerate.
Another issue is whether or not a feedrate override reduction causes a deceleration
that acts over several planner blocks. For example, say that the plan is already
heavily decelerating throughout it, reducing the feedrate will not do much to it. So,
how do we determine when to resume the new plan? How many blocks do we have to wait
until the new plan intersects with the deceleration curve? One plus though, the
deceleration will never be more than the number of blocks in the entire planner buffer,
but it theoretically can be equal to it when all planner blocks are decelerating already.
*/
void st_prep_buffer()
{
if (sys.state == STATE_QUEUED) { return; } // Block until a motion state is issued
while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer.
// Initialize new segment
st_segment_t *prep_segment = &segment_buffer[segment_buffer_head];
prep_segment->flag = SEGMENT_NOOP;
// Determine if we need to load a new planner block.
if (pl_prep_block == NULL) {
pl_prep_block = plan_get_block_by_index(pl_prep_index); // Query planner for a queued block
if (pl_prep_block == NULL) { return; } // No planner blocks. Exit.
// Increment stepper common data index
if ( ++st_data_prep_index == (SEGMENT_BUFFER_SIZE-1) ) { st_data_prep_index = 0; }
// Check if the planner has re-computed this block mid-execution. If so, push the previous segment
// data. Otherwise, prepare a new segment data for the new planner block.
if (pl_partial_block_flag) {
// Prepare new shared segment block data and copy the relevant last segment block data.
st_data_t *last_st_prep_data;
last_st_prep_data = st_prep_data;
st_prep_data = &segment_data[st_data_prep_index];
st_prep_data->step_events_remaining = last_st_prep_data->step_events_remaining;
st_prep_data->rate_delta = last_st_prep_data->rate_delta;
st_prep_data->dist_per_step = last_st_prep_data->dist_per_step;
st_prep_data->nominal_rate = last_st_prep_data->nominal_rate; // TODO: Feedrate overrides recomputes this.
st_prep_data->mm_per_step = last_st_prep_data->mm_per_step;
pl_partial_block_flag = false; // Reset flag
} else {
// Prepare commonly shared planner block data for the ensuing segment buffer moves ad-hoc, since
// the planner buffer can dynamically change the velocity profile data as blocks are added.
st_prep_data = &segment_data[st_data_prep_index];
// Initialize Bresenham variables
st_prep_data->step_events_remaining = pl_prep_block->step_event_count;
// Convert planner block velocity profile data to stepper rate and step distance data.
st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
st_prep_data->rate_delta = ceil(pl_prep_block->acceleration*
((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic)
st_prep_data->dist_per_step = ceil((pl_prep_block->millimeters*INV_TIME_MULTIPLIER)/pl_prep_block->step_event_count); // (mult*mm/step)
// TODO: Check if we really need to store this.
st_prep_data->mm_per_step = pl_prep_block->millimeters/pl_prep_block->step_event_count;
}
// Convert planner entry speed to stepper initial rate.
st_prep_data->initial_rate = ceil(sqrt(pl_prep_block->entry_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
// TODO: Nominal rate changes with feedrate override.
// st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
st_prep_data->current_approx_rate = st_prep_data->initial_rate;
// Calculate the planner block velocity profile type, determine deceleration point, and initial ramp.
float mm_decelerate_after = plan_calculate_velocity_profile(pl_prep_index);
st_prep_data->decelerate_after = ceil( mm_decelerate_after/st_prep_data->mm_per_step );
if (st_prep_data->decelerate_after > 0) { // If 0, RAMP_CHANGE_DECEL flag is set later.
if (st_prep_data->initial_rate != st_prep_data->nominal_rate) { prep_segment->flag = RAMP_CHANGE_ACCEL; }
}
}
// Set new segment to point to the current segment data block.
prep_segment->st_data_index = st_data_prep_index;
// Approximate the velocity over the new segment using the already computed rate values.
// NOTE: This assumes that each segment will have an execution time roughly equal to every ACCELERATION_TICK.
// We do this to minimize memory and computational requirements. However, this could easily be replaced with
// a more exact approximation or have an user-defined time per segment, if CPU and memory overhead allows.
if (st_prep_data->decelerate_after <= 0) {
if (st_prep_data->decelerate_after == 0) { prep_segment->flag = RAMP_CHANGE_DECEL; } // Set segment deceleration flag
else { st_prep_data->current_approx_rate -= st_prep_data->rate_delta; }
if (st_prep_data->current_approx_rate < st_prep_data->rate_delta) { st_prep_data->current_approx_rate >>= 1; }
} else {
if (st_prep_data->current_approx_rate < st_prep_data->nominal_rate) {
st_prep_data->current_approx_rate += st_prep_data->rate_delta;
if (st_prep_data->current_approx_rate > st_prep_data->nominal_rate) {
st_prep_data->current_approx_rate = st_prep_data->nominal_rate;
}
}
}
// TODO: Look into replacing the following dist_per_step divide with multiplying its inverse to save cycles.
// Compute the number of steps in the prepped segment based on the approximate current rate.
// NOTE: The dist_per_step divide cancels out the INV_TIME_MULTIPLIER and converts the rate value to steps.
prep_segment->n_step = ceil(max(MINIMUM_STEP_RATE,st_prep_data->current_approx_rate)*
(ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND)/st_prep_data->dist_per_step);
// NOTE: Ensures it moves for very slow motions, but MINIMUM_STEP_RATE should always set this too. Perhaps
// a compile-time check to see if MINIMUM_STEP_RATE is set high enough is all that is needed.
prep_segment->n_step = max(prep_segment->n_step,MINIMUM_STEPS_PER_SEGMENT);
// NOTE: As long as the ACCELERATION_TICKS_PER_SECOND is valid, n_step should never exceed 255 and overflow.
// prep_segment->n_step = min(prep_segment->n_step,MAXIMUM_STEPS_PER_BLOCK); // Prevent unsigned int8 overflow.
// Check if n_step exceeds steps remaining in planner block. If so, truncate.
if (prep_segment->n_step > st_prep_data->step_events_remaining) {
prep_segment->n_step = st_prep_data->step_events_remaining;
}
// Check if n_step crosses decelerate point in block. If so, truncate to ensure the deceleration
// ramp counters are set correctly during execution.
if (st_prep_data->decelerate_after > 0) {
if (prep_segment->n_step > st_prep_data->decelerate_after) {
prep_segment->n_step = st_prep_data->decelerate_after;
}
}
// Update stepper common data variables.
st_prep_data->decelerate_after -= prep_segment->n_step;
st_prep_data->step_events_remaining -= prep_segment->n_step;
// Check for end of planner block
if ( st_prep_data->step_events_remaining == 0 ) {
// TODO: When a feed hold ends, the step_events_remaining will also be zero, even though a block
// have partially been completed. We need to flag the stepper algorithm to indicate a stepper shutdown
// when complete, but not remove the planner block unless it truly is the end of the block (rare).
// Set EOB bitflag so stepper algorithm discards the planner block after this segment completes.
prep_segment->flag |= SEGMENT_END_OF_BLOCK;
// Move planner pointer to next block and flag to load a new block for the next segment.
pl_prep_index = plan_next_block_index(pl_prep_index);
pl_prep_block = NULL;
}
// New step segment completed. Increment segment buffer indices.
segment_buffer_head = segment_next_head;
if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { segment_next_head = 0; }
// long a = prep_segment->n_step;
// printInteger(a);
// printString(" ");
}
}
uint8_t st_get_prep_block_index()
{
// Returns only the index but doesn't state if the block has been partially executed. How do we simply check for this?
return(pl_prep_index);
}
void st_fetch_partial_block_parameters(uint8_t block_index, float *millimeters_remaining, uint8_t *is_decelerating)
{
// if called, can we assume that this always changes and needs to be updated? if so, then
// we can perform all of the segment buffer setup tasks here to make sure the next time
// the segments are loaded, the st_data buffer is updated correctly.
// !!! Make sure that this is always pointing to the correct st_prep_data block.
// When a mid-block acceleration occurs, we have to make sure the ramp counters are updated
// correctly, much in the same fashion as the deceleration counters. Need to think about this
// make sure this is right, but i'm pretty sure it is.
// TODO: NULL means that the segment buffer has just completed a planner block. Clean up!
if (pl_prep_block != NULL) {
*millimeters_remaining = st_prep_data->step_events_remaining*st_prep_data->mm_per_step;
if (st_prep_data->decelerate_after > 0) { *is_decelerating = false; }
else { *is_decelerating = true; }
// Flag for new prep_block when st_prep_buffer() is called after the planner recomputes.
pl_partial_block_flag = true;
pl_prep_block = NULL;
}
return;
} }
uint8_t st_is_decelerating() { uint8_t st_is_decelerating() {

View File

@ -45,7 +45,10 @@ void st_cycle_reinitialize();
// Initiates a feed hold of the running program // Initiates a feed hold of the running program
void st_feed_hold(); void st_feed_hold();
// Accessor function to query the acceleration state of the stepper void st_prep_buffer();
uint8_t st_is_decelerating();
uint8_t st_get_prep_block_index();
void st_fetch_partial_block_parameters(uint8_t block_index, float *millimeters_remaining, uint8_t *is_decelerating);
#endif #endif

746
stepper_old.c Normal file
View File

@ -0,0 +1,746 @@
/*
stepper.c - stepper motor driver: executes motion plans using stepper motors
Part of Grbl
Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
#include <avr/interrupt.h>
#include "stepper.h"
#include "config.h"
#include "settings.h"
#include "planner.h"
#include "nuts_bolts.h"
// Some useful constants
#define TICKS_PER_MICROSECOND (F_CPU/1000000)
#define RAMP_NOOP_CRUISE 0
#define RAMP_ACCEL 1
#define RAMP_DECEL 2
#define LOAD_NOOP 0
#define LOAD_SEGMENT 1
#define LOAD_BLOCK 2
#define SEGMENT_NOOP 0
#define SEGMENT_END_OF_BLOCK bit(0)
#define SEGMENT_ACCEL bit(1)
#define SEGMENT_DECEL bit(2)
#define MINIMUM_STEPS_PER_SEGMENT 1 // Don't change
#define SEGMENT_BUFFER_SIZE 6
// Stepper state variable. Contains running data and trapezoid variables.
typedef struct {
// Used by the bresenham line algorithm
int32_t counter_x, // Counter variables for the bresenham line tracer
counter_y,
counter_z;
uint8_t segment_steps_remaining; // Steps remaining in line segment motion
// Used by inverse time algorithm to track step rate
int32_t counter_d; // Inverse time distance traveled since last step event
uint32_t delta_d; // Inverse time distance traveled per interrupt tick
uint32_t d_per_tick;
// Used by the stepper driver interrupt
uint8_t execute_step; // Flags step execution for each interrupt.
uint8_t step_pulse_time; // Step pulse reset time after step rise
uint8_t out_bits; // The next stepping-bits to be output
uint8_t load_flag;
uint8_t ramp_count;
uint8_t ramp_type;
} stepper_t;
static stepper_t st;
// Stores stepper common data for executing steps in the segment buffer. Data can change mid-block when the
// planner updates the remaining block velocity profile with a more optimal plan or a feedrate override occurs.
// NOTE: Normally, this buffer is partially in-use, but, for the worst case scenario, it will never exceed
// the number of accessible stepper buffer segments (SEGMENT_BUFFER_SIZE-1).
typedef struct {
int32_t step_events_remaining; // Tracks step event count for the executing planner block
uint32_t d_next; // Scaled distance to next step
uint32_t initial_rate; // Initialized step rate at re/start of a planner block
uint32_t nominal_rate; // The nominal step rate for this block in step_events/minute
uint32_t rate_delta; // The steps/minute to add or subtract when changing speed (must be positive)
uint32_t current_approx_rate; // Tracks the approximate segment rate to predict steps per segment to execute
int32_t decelerate_after; // Tracks when to initiate deceleration according to the planner block
float mm_per_step;
} st_data_t;
static st_data_t segment_data[SEGMENT_BUFFER_SIZE-1];
// Primary stepper segment ring buffer. Contains small, short line segments for the stepper algorithm to execute,
// which are "checked-out" incrementally from the first block in the planner buffer. Once "checked-out", the steps
// in the segments buffer cannot be modified by the planner, where the remaining planner block steps still can.
typedef struct {
uint8_t n_step; // Number of step events to be executed for this segment
uint8_t st_data_index; // Stepper buffer common data index. Uses this information to execute this segment.
uint8_t flag; // Stepper algorithm bit-flag for special execution conditions.
} st_segment_t;
static st_segment_t segment_buffer[SEGMENT_BUFFER_SIZE];
// Step segment ring buffer indices
static volatile uint8_t segment_buffer_tail;
static volatile uint8_t segment_buffer_head;
static uint8_t segment_next_head;
static volatile uint8_t busy; // Used to avoid ISR nesting of the "Stepper Driver Interrupt". Should never occur though.
static plan_block_t *pl_current_block; // A pointer to the planner block currently being traced
static st_segment_t *st_current_segment;
static st_data_t *st_current_data;
// Pointers for the step segment being prepped from the planner buffer. Accessed only by the
// main program. Pointers may be planning segments or planner blocks ahead of what being executed.
static plan_block_t *pl_prep_block; // Pointer to the planner block being prepped
static st_data_t *st_prep_data; // Pointer to the stepper common data being prepped
static uint8_t pl_prep_index; // Index of planner block being prepped
static uint8_t st_data_prep_index; // Index of stepper common data block being prepped
static uint8_t pl_partial_block_flag; // Flag indicating the planner has modified the prepped planner block
/* __________________________
/| |\ _________________ ^
/ | | \ /| |\ |
/ | | \ / | | \ s
/ | | | | | \ p
/ | | | | | \ e
+-----+------------------------+---+--+---------------+----+ e
| BLOCK 1 | BLOCK 2 | d
time ----->
The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates by block->rate_delta
until reaching cruising speed block->nominal_rate, and/or until step_events_remaining reaches block->decelerate_after
after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as
+/- block->rate_delta velocity increments by the midpoint rule at each ACCELERATION_TICKS_PER_SECOND.
*/
// Stepper state initialization. Cycle should only start if the st.cycle_start flag is
// enabled. Startup init and limits call this function but shouldn't start the cycle.
void st_wake_up()
{
// Enable steppers by resetting the stepper disable port
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) {
STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT);
} else {
STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
}
if (sys.state == STATE_CYCLE) {
// Initialize stepper output bits
st.out_bits = settings.invert_mask;
// Initialize step pulse timing from settings.
st.step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3);
// Enable stepper driver interrupt
st.execute_step = false;
st.load_flag = LOAD_BLOCK;
TCNT2 = 0; // Clear Timer2
TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt
TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler
}
}
// Stepper shutdown
void st_go_idle()
{
// Disable stepper driver interrupt. Allow Timer0 to finish. It will disable itself.
TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt
TCCR2B = 0; // Disable Timer2
busy = false;
// Disable steppers only upon system alarm activated or by user setting to not be kept enabled.
if ((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) {
// Force stepper dwell to lock axes for a defined amount of time to ensure the axes come to a complete
// stop and not drift from residual inertial forces at the end of the last movement.
delay_ms(settings.stepper_idle_lock_time);
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) {
STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
} else {
STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT);
}
}
}
/* "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based
on an inverse time stepper algorithm, where a timer ticks at a constant frequency and uses
time-distance counters to track when its the approximate time for a step event. For reference,
a similar inverse-time algorithm by Pramod Ranade is susceptible to numerical round-off,
meaning that some axes steps may not execute correctly for a given multi-axis motion.
Grbl's algorithm differs by using a single inverse time-distance counter to manage a
Bresenham line algorithm for multi-axis step events, which ensures the number of steps for
each axis are executed exactly. In other words, Grbl uses a Bresenham within a Bresenham
algorithm, where one tracks time for step events and the other steps for multi-axis moves.
Grbl specifically uses the Bresenham algorithm due to its innate mathematical exactness and
low computational overhead, requiring simple integer +,- counters only.
This interrupt pops blocks from the step segment buffer and executes them by pulsing the
stepper pins appropriately. It is supported by The Stepper Port Reset Interrupt which it uses
to reset the stepper port after each pulse. The bresenham line tracer algorithm controls all
three stepper outputs simultaneously with these two interrupts.
*/
/* TODO:
- Measure time in ISR. Typical and worst-case. Should be virtually identical to last algorithm.
There are no major changes to the base operations of this ISR with the new segment buffer.
- Write how the acceleration counters work and why they are set at half via mid-point rule.
- Determine if placing the position counters elsewhere (or change them to 8-bit variables that
are added to the system position counters at the end of a segment) frees up cycles.
- Write a blurb about how the acceleration should be handled within the ISR. All of the
time/step/ramp counters accurately keep track of the remainders and phasing of the variables
with time. This means we do not have to compute them via expensive floating point beforehand.
- Need to do an analysis to determine if these counters are really that much cheaper. At least
find out when it isn't anymore. Particularly when the ISR is at a very high frequency.
*/
ISR(TIMER2_COMPA_vect)
{
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
if (busy) { return; } // The busy-flag is used to avoid reentering this interrupt
// Pulse stepper port pins, if flagged. New block dir will always be set one timer tick
// before any step pulse due to algorithm design.
if (st.execute_step) {
st.execute_step = false;
STEPPING_PORT = ( STEPPING_PORT & ~(DIRECTION_MASK | STEP_MASK) ) | st.out_bits;
TCNT0 = st.step_pulse_time; // Reload Timer0 counter.
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
}
busy = true;
sei(); // Re-enable interrupts to allow Stepper Port Reset Interrupt to fire on-time.
// NOTE: The remaining code in this ISR will finish before returning to main program.
// If there is no step segment, attempt to pop one from the stepper buffer
if (st.load_flag != LOAD_NOOP) {
// Anything in the buffer? If so, load and initialize next step segment.
if (segment_buffer_head != segment_buffer_tail) {
// NOTE: Loads after a step event. At high rates above 1/2 ISR frequency, there is
// a small chance that this will load at the same time as a step event. Hopefully,
// the overhead for this loading event isn't too much.. possibly 2-5 usec.
// NOTE: The stepper algorithm must control the planner buffer tail as it completes
// the block moves. Otherwise, a feed hold can leave a few step buffer line moves
// without the correct planner block information.
st_current_segment = &segment_buffer[segment_buffer_tail];
// Load number of steps to execute from stepper buffer
st.segment_steps_remaining = st_current_segment->n_step;
// Check if the counters need to be reset for a new planner block
if (st.load_flag == LOAD_BLOCK) {
pl_current_block = plan_get_current_block(); // Should always be there. Stepper buffer handles this.
st_current_data = &segment_data[segment_buffer[segment_buffer_tail].st_data_index]; //st_current_segment->st_data_index];
// Initialize direction bits for block
st.out_bits = pl_current_block->direction_bits ^ settings.invert_mask;
st.execute_step = true; // Set flag to set direction bits upon next ISR tick.
// Initialize Bresenham line counters
st.counter_x = (pl_current_block->step_event_count >> 1);
st.counter_y = st.counter_x;
st.counter_z = st.counter_x;
// Initialize inverse time and step rate counter data
st.counter_d = st_current_data->d_next; // d_next always greater than delta_d.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_per_tick = MINIMUM_STEP_RATE; }
else { st.d_per_tick = st.delta_d; }
// During feed hold, do not update rate, ramp type, or ramp counters. Keep decelerating.
// if (sys.state == STATE_CYCLE) {
st.delta_d = st_current_data->initial_rate;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Initialize ramp counter via midpoint rule
st.ramp_type = RAMP_NOOP_CRUISE; // Initialize as no ramp operation. Corrected later if necessary.
// }
}
// Acceleration and cruise handled by ramping. Just check if deceleration needs to begin.
if ( st_current_segment->flag & (SEGMENT_DECEL | SEGMENT_ACCEL) ) {
/* Compute correct ramp count for a ramp change. Upon a switch from acceleration to deceleration,
or vice-versa, the new ramp count must be set to trigger the next acceleration tick equal to
the number of ramp ISR ticks counted since the last acceleration tick. This is ensures the
ramp is executed exactly as the plan dictates. Otherwise, when a ramp begins from a known
rate (nominal/cruise or initial), the ramp count must be set to ISR_TICKS_PER_ACCELERATION_TICK/2
as mandated by the mid-point rule. For these conditions, the ramp count have been initialized
such that the following computation is still correct. */
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK-st.ramp_count;
if ( st_current_segment->flag & SEGMENT_DECEL ) { st.ramp_type = RAMP_DECEL; }
else { st.ramp_type = RAMP_ACCEL; }
}
st.load_flag = LOAD_NOOP; // Segment motion loaded. Set no-operation flag to skip during execution.
} else {
// Can't discard planner block here if a feed hold stops in middle of block.
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end
return; // Nothing to do but exit.
}
}
// Adjust inverse time counter for ac/de-celerations
// NOTE: Accelerations are handled by the stepper algorithm as it's thought to be more computationally
// efficient on the Arduino AVR. This could may not be true with higher ISR frequencies or faster CPUs.
if (st.ramp_type) { // Ignored when ramp type is NOOP_CRUISE
st.ramp_count--; // Tick acceleration ramp counter
if (st.ramp_count == 0) { // Adjust step rate when its time
if (st.ramp_type == RAMP_ACCEL) { // Adjust velocity for acceleration
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
st.delta_d += st_current_data->rate_delta;
if (st.delta_d >= st_current_data->nominal_rate) { // Reached nominal rate.
st.delta_d = st_current_data->nominal_rate; // Set cruising velocity
st.ramp_type = RAMP_NOOP_CRUISE; // Set ramp flag to cruising
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Re-initialize counter for next ramp.
}
} else { // Adjust velocity for deceleration.
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
if (st.delta_d > st_current_data->rate_delta) {
st.delta_d -= st_current_data->rate_delta;
} else { // Moving near zero feed rate. Gracefully slow down.
st.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow.
// TODO: Check for and handle feed hold exit? At this point, machine is stopped.
// - Set system flag to recompute plan and reset segment buffer.
// - Segment steps in buffer needs to be returned to planner correctly.
// busy = false;
// return;
}
}
// Finalize adjusted step rate. Ensure minimum.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_per_tick = MINIMUM_STEP_RATE; }
else { st.d_per_tick = st.delta_d; }
}
}
// Iterate inverse time counter. Triggers each Bresenham step event.
st.counter_d -= st.d_per_tick;
// Execute Bresenham step event, when it's time to do so.
if (st.counter_d < 0) {
st.counter_d += st_current_data->d_next; // Reload inverse time counter
st.out_bits = pl_current_block->direction_bits; // Reset out_bits and reload direction bits
st.execute_step = true;
// Execute step displacement profile by Bresenham line algorithm
st.counter_x -= pl_current_block->steps[X_AXIS];
if (st.counter_x < 0) {
st.out_bits |= (1<<X_STEP_BIT);
st.counter_x += pl_current_block->step_event_count;
// st.steps_x++;
if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; }
}
st.counter_y -= pl_current_block->steps[Y_AXIS];
if (st.counter_y < 0) {
st.out_bits |= (1<<Y_STEP_BIT);
st.counter_y += pl_current_block->step_event_count;
// st.steps_y++;
if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; }
}
st.counter_z -= pl_current_block->steps[Z_AXIS];
if (st.counter_z < 0) {
st.out_bits |= (1<<Z_STEP_BIT);
st.counter_z += pl_current_block->step_event_count;
// st.steps_z++;
if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; }
}
// Check step events for trapezoid change or end of block.
st.segment_steps_remaining--; // Decrement step events count
if (st.segment_steps_remaining == 0) {
/*
NOTE: sys.position updates could be done here. The bresenham counters can have
their own fast 8-bit addition-only counters. Here we would check the direction and
apply it to sys.position accordingly. However, this could take too much time
combined with loading a new segment during next cycle too.
TODO: Measure the time it would take in the worst case. It could still be faster
overall during segment execution if uint8 step counters tracked this and was added
to the system position variables here. Compared to worst case now, it wouldn't be
that much different.
// TODO: Upon loading, step counters would need to be zeroed.
// TODO: For feedrate overrides, we will have to execute add these values.. although
// for probing, this breaks. Current values won't be correct, unless we query it.
// It makes things more complicated, but still manageable.
if (st.steps_x > 0) {
if (st.out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS] += st.steps_x; }
else { sys.position[X_AXIS] -= st.steps_x; }
}
if (st.steps_y > 0) {
if (st.out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS] += st.steps_y; }
else { sys.position[Y_AXIS] -= st.steps_y; }
}
if (st.steps_z > 0) {
if (st.out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS] += st.steps_z; }
else { sys.position[Z_AXIS] -= st.steps_z; }
}
*/
// Line move is complete, set load line flag to check for new move.
// Check if last line move in planner block. Discard if so.
if (st_current_segment->flag & SEGMENT_END_OF_BLOCK) {
plan_discard_current_block();
st.load_flag = LOAD_BLOCK;
} else {
st.load_flag = LOAD_SEGMENT;
}
// Discard current segment by advancing buffer tail index
if ( ++segment_buffer_tail == SEGMENT_BUFFER_SIZE) { segment_buffer_tail = 0; }
}
st.out_bits ^= settings.invert_mask; // Apply step port invert mask
}
busy = false;
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT;
}
// 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.
ISR(TIMER0_OVF_vect)
{
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.invert_mask & STEP_MASK);
TCCR0B = 0; // Disable timer until needed.
}
// Reset and clear stepper subsystem variables
void st_reset()
{
memset(&st, 0, sizeof(st));
st.load_flag = LOAD_BLOCK;
busy = false;
pl_current_block = NULL; // Planner block pointer used by stepper algorithm
pl_prep_block = NULL; // Planner block pointer used by segment buffer
pl_prep_index = 0; // Planner buffer indices are also reset to zero.
st_data_prep_index = 0;
segment_buffer_tail = 0;
segment_buffer_head = 0; // empty = tail
segment_next_head = 1;
pl_partial_block_flag = false;
}
// Initialize and start the stepper motor subsystem
void st_init()
{
// Configure directions of interface pins
STEPPING_DDR |= STEPPING_MASK;
STEPPING_PORT = (STEPPING_PORT & ~STEPPING_MASK) | settings.invert_mask;
STEPPERS_DISABLE_DDR |= 1<<STEPPERS_DISABLE_BIT;
// Configure Timer 2
TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt while configuring it
TCCR2B = 0; // Disable Timer2 until needed
TCNT2 = 0; // Clear Timer2 counter
TCCR2A = (1<<WGM21); // Set CTC mode
OCR2A = (F_CPU/ISR_TICKS_PER_SECOND)/8 - 1; // Set Timer2 CTC rate
// Configure Timer 0
TIMSK0 &= ~(1<<TOIE0);
TCCR0A = 0; // Normal operation
TCCR0B = 0; // Disable Timer0 until needed
TIMSK0 |= (1<<TOIE0); // Enable overflow interrupt
// Start in the idle state, but first wake up to check for keep steppers enabled option.
st_wake_up();
st_go_idle();
}
// Planner external interface to start stepper interrupt and execute the blocks in queue. Called
// by the main program functions: planner auto-start and run-time command execution.
void st_cycle_start()
{
if (sys.state == STATE_QUEUED) {
sys.state = STATE_CYCLE;
st_wake_up();
}
}
// Execute a feed hold with deceleration, only during cycle. Called by main program.
void st_feed_hold()
{
if (sys.state == STATE_CYCLE) {
sys.state = STATE_HOLD;
sys.auto_start = false; // Disable planner auto start upon feed hold.
}
}
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
// runtime command execution in the main program, ensuring that the planner re-plans safely.
// NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
// cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
// Only the planner de/ac-celerations profiles and stepper rates have been updated.
void st_cycle_reinitialize()
{
// if (pl_current_block != NULL) {
// Replan buffer from the feed hold stop location.
// TODO: Need to add up all of the step events in the current planner block to give
// back to the planner. Should only need it for the current block.
// BUT! The planner block millimeters is all changed and may be changed into the next
// planner block. The block millimeters would need to be recalculated via step counts
// and the mm/step variable.
// OR. Do we plan the feed hold itself down with the planner.
// plan_cycle_reinitialize(st_current_data->step_events_remaining);
// st.ramp_type = RAMP_ACCEL;
// st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
// st.delta_d = 0;
// sys.state = STATE_QUEUED;
// } else {
// sys.state = STATE_IDLE;
// }
sys.state = STATE_IDLE;
}
/* Prepares step segment buffer. Continuously called from main program.
The segment buffer is an intermediary buffer interface between the execution of steps
by the stepper algorithm and the velocity profiles generated by the planner. The stepper
algorithm only executes steps within the segment buffer and is filled by the main program
when steps are "checked-out" from the first block in the planner buffer. This keeps the
step execution and planning optimization processes atomic and protected from each other.
The number of steps "checked-out" from the planner buffer and the number of segments in
the segment buffer is sized and computed such that no operation in the main program takes
longer than the time it takes the stepper algorithm to empty it before refilling it.
Currently, the segment buffer conservatively holds roughly up to 40-60 msec of steps.
NOTE: The segment buffer executes a set number of steps over an approximate time period.
If we try to execute over a set time period, it is difficult to guarantee or predict how
many steps will execute over it, especially when the step pulse phasing between the
neighboring segments are kept consistent. Meaning that, if the last segment step pulses
right before its end, the next segment must delay its first pulse so that the step pulses
are consistently spaced apart over time to keep the step pulse train nice and smooth.
Keeping track of phasing and ensuring that the exact number of steps are executed as
defined by the planner block, the related computational overhead gets quickly and
prohibitively expensive, especially in real-time.
Since the stepper algorithm automatically takes care of the step pulse phasing with
its ramp and inverse time counters, we don't have to explicitly and expensively track the
exact number of steps, time, or phasing of steps. All we need to do is approximate
the number of steps in each segment such that the segment buffer has enough execution time
for the main program to do what it needs to do and refill it when it has time. In other
words, we just need to compute a cheap approximation of the current velocity and the
number of steps over it.
*/
/*
TODO: Figure out how to enforce a deceleration when a feedrate override is reduced.
The problem is that when an override is reduced, the planner may not plan back to
the current rate. Meaning that the velocity profiles for certain conditions no longer
are trapezoidal or triangular. For example, if the current block is cruising at a
nominal rate and the feedrate override is reduced, the new nominal rate will now be
lower. The velocity profile must first decelerate to the new nominal rate and then
follow on the new plan. So the remaining velocity profile will have a decelerate,
cruise, and another decelerate.
Another issue is whether or not a feedrate override reduction causes a deceleration
that acts over several planner blocks. For example, say that the plan is already
heavily decelerating throughout it, reducing the feedrate will not do much to it. So,
how do we determine when to resume the new plan? How many blocks do we have to wait
until the new plan intersects with the deceleration curve? One plus though, the
deceleration will never be more than the number of blocks in the entire planner buffer,
but it theoretically can be equal to it when all planner blocks are decelerating already.
*/
void st_prep_buffer()
{
while (segment_buffer_tail != segment_next_head) { // Check if we need to fill the buffer.
// Initialize new segment
st_segment_t *prep_segment = &segment_buffer[segment_buffer_head];
prep_segment->flag = SEGMENT_NOOP;
// Determine if we need to load a new planner block.
if (pl_prep_block == NULL) {
pl_prep_block = plan_get_block_by_index(pl_prep_index); // Query planner for a queued block
if (pl_prep_block == NULL) { return; } // No planner blocks. Exit.
// Increment stepper common data index
if ( ++st_data_prep_index == (SEGMENT_BUFFER_SIZE-1) ) { st_data_prep_index = 0; }
// Check if the planner has re-computed this block mid-execution. If so, push the previous segment
// data. Otherwise, prepare a new segment data for the new planner block.
if (pl_partial_block_flag) {
// Prepare new shared segment block data and copy the relevant last segment block data.
st_data_t *last_st_prep_data;
last_st_prep_data = st_prep_data;
st_prep_data = &segment_data[st_data_prep_index];
st_prep_data->step_events_remaining = last_st_prep_data->step_events_remaining;
st_prep_data->rate_delta = last_st_prep_data->rate_delta;
st_prep_data->d_next = last_st_prep_data->d_next;
st_prep_data->nominal_rate = last_st_prep_data->nominal_rate; // TODO: Feedrate overrides recomputes this.
st_prep_data->mm_per_step = last_st_prep_data->mm_per_step;
pl_partial_block_flag = false; // Reset flag
} else {
// Prepare commonly shared planner block data for the ensuing segment buffer moves ad-hoc, since
// the planner buffer can dynamically change the velocity profile data as blocks are added.
st_prep_data = &segment_data[st_data_prep_index];
// Initialize Bresenham variables
st_prep_data->step_events_remaining = pl_prep_block->step_event_count;
// Convert planner block velocity profile data to stepper rate and step distance data.
st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
st_prep_data->rate_delta = ceil(pl_prep_block->acceleration*
((INV_TIME_MULTIPLIER/(60.0*60.0))/(ISR_TICKS_PER_SECOND*ACCELERATION_TICKS_PER_SECOND))); // (mult*mm/isr_tic/accel_tic)
st_prep_data->d_next = ceil((pl_prep_block->millimeters*INV_TIME_MULTIPLIER)/pl_prep_block->step_event_count); // (mult*mm/step)
// TODO: Check if we really need to store this.
st_prep_data->mm_per_step = pl_prep_block->millimeters/pl_prep_block->step_event_count;
}
// Convert planner entry speed to stepper initial rate.
st_prep_data->initial_rate = ceil(sqrt(pl_prep_block->entry_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
// TODO: Nominal rate changes with feedrate override.
// st_prep_data->nominal_rate = ceil(sqrt(pl_prep_block->nominal_speed_sqr)*(INV_TIME_MULTIPLIER/(60.0*ISR_TICKS_PER_SECOND))); // (mult*mm/isr_tic)
st_prep_data->current_approx_rate = st_prep_data->initial_rate;
// Calculate the planner block velocity profile type, determine deceleration point, and initial ramp.
float mm_decelerate_after = plan_calculate_velocity_profile(pl_prep_index);
st_prep_data->decelerate_after = ceil( mm_decelerate_after/st_prep_data->mm_per_step );
if (st_prep_data->decelerate_after > 0) { // If 0, SEGMENT_DECEL flag is set later.
if (st_prep_data->initial_rate != st_prep_data->nominal_rate) { prep_segment->flag = SEGMENT_ACCEL; }
}
}
// Set new segment to point to the current segment data block.
prep_segment->st_data_index = st_data_prep_index;
// Approximate the velocity over the new segment using the already computed rate values.
// NOTE: This assumes that each segment will have an execution time roughly equal to every ACCELERATION_TICK.
// We do this to minimize memory and computational requirements. However, this could easily be replaced with
// a more exact approximation or have a unique time per segment, if CPU and memory overhead allows.
if (st_prep_data->decelerate_after <= 0) {
if (st_prep_data->decelerate_after == 0) { prep_segment->flag = SEGMENT_DECEL; } // Set segment deceleration flag
else { st_prep_data->current_approx_rate -= st_prep_data->rate_delta; }
if (st_prep_data->current_approx_rate < st_prep_data->rate_delta) { st_prep_data->current_approx_rate >>= 1; }
} else {
if (st_prep_data->current_approx_rate < st_prep_data->nominal_rate) {
st_prep_data->current_approx_rate += st_prep_data->rate_delta;
if (st_prep_data->current_approx_rate > st_prep_data->nominal_rate) {
st_prep_data->current_approx_rate = st_prep_data->nominal_rate;
}
}
}
// Compute the number of steps in the prepped segment based on the approximate current rate.
// NOTE: The d_next divide cancels out the INV_TIME_MULTIPLIER and converts the rate value to steps.
prep_segment->n_step = ceil(max(MINIMUM_STEP_RATE,st_prep_data->current_approx_rate)*
(ISR_TICKS_PER_SECOND/ACCELERATION_TICKS_PER_SECOND)/st_prep_data->d_next);
// NOTE: Ensures it moves for very slow motions, but MINIMUM_STEP_RATE should always set this too. Perhaps
// a compile-time check to see if MINIMUM_STEP_RATE is set high enough is all that is needed.
prep_segment->n_step = max(prep_segment->n_step,MINIMUM_STEPS_PER_SEGMENT);
// NOTE: As long as the ACCELERATION_TICKS_PER_SECOND is valid, n_step should never exceed 255 and overflow.
// prep_segment->n_step = min(prep_segment->n_step,MAXIMUM_STEPS_PER_BLOCK); // Prevent unsigned int8 overflow.
// Check if n_step exceeds steps remaining in planner block. If so, truncate.
if (prep_segment->n_step > st_prep_data->step_events_remaining) {
prep_segment->n_step = st_prep_data->step_events_remaining;
}
// Check if n_step crosses decelerate point in block. If so, truncate to ensure the deceleration
// ramp counters are set correctly during execution.
if (st_prep_data->decelerate_after > 0) {
if (prep_segment->n_step > st_prep_data->decelerate_after) {
prep_segment->n_step = st_prep_data->decelerate_after;
}
}
// Update stepper common data variables.
st_prep_data->decelerate_after -= prep_segment->n_step;
st_prep_data->step_events_remaining -= prep_segment->n_step;
// Check for end of planner block
if ( st_prep_data->step_events_remaining == 0 ) {
// Set EOB bitflag so stepper algorithm discards the planner block after this segment completes.
prep_segment->flag |= SEGMENT_END_OF_BLOCK;
// Move planner pointer to next block and flag to load a new block for the next segment.
pl_prep_index = plan_next_block_index(pl_prep_index);
pl_prep_block = NULL;
}
// New step segment completed. Increment segment buffer indices.
segment_buffer_head = segment_next_head;
if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { segment_next_head = 0; }
// long a = prep_segment->n_step;
// printInteger(a);
// printString(" ");
}
}
uint8_t st_get_prep_block_index()
{
// Returns only the index but doesn't state if the block has been partially executed. How do we simply check for this?
return(pl_prep_index);
}
void st_fetch_partial_block_parameters(uint8_t block_index, float *millimeters_remaining, uint8_t *is_decelerating)
{
// if called, can we assume that this always changes and needs to be updated? if so, then
// we can perform all of the segment buffer setup tasks here to make sure the next time
// the segments are loaded, the st_data buffer is updated correctly.
// !!! Make sure that this is always pointing to the correct st_prep_data block.
// When a mid-block acceleration occurs, we have to make sure the ramp counters are updated
// correctly, much in the same fashion as the deceleration counters. Need to think about this
// make sure this is right, but i'm pretty sure it is.
// TODO: NULL means that the segment buffer has just completed a planner block. Clean up!
if (pl_prep_block != NULL) {
*millimeters_remaining = st_prep_data->step_events_remaining*st_prep_data->mm_per_step;
if (st_prep_data->decelerate_after > 0) { *is_decelerating = false; }
else { *is_decelerating = true; }
// Flag for new prep_block when st_prep_buffer() is called after the planner recomputes.
pl_partial_block_flag = true;
pl_prep_block = NULL;
}
return;
}

387
stepper_v0_9.c Normal file
View File

@ -0,0 +1,387 @@
/*
stepper.c - stepper motor driver: executes motion plans using stepper motors
Part of Grbl
Copyright (c) 2011-2013 Sungeun K. Jeon
Copyright (c) 2009-2011 Simen Svale Skogsrud
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
*/
/* The timer calculations of this module informed by the 'RepRap cartesian firmware' by Zack Smith
and Philipp Tiefenbacher. */
#include <avr/interrupt.h>
#include "stepper.h"
#include "config.h"
#include "settings.h"
#include "planner.h"
// Some useful constants
#define TICKS_PER_MICROSECOND (F_CPU/1000000)
#define CRUISE_RAMP 0
#define ACCEL_RAMP 1
#define DECEL_RAMP 2
// Stepper state variable. Contains running data and trapezoid variables.
typedef struct {
// Used by the bresenham line algorithm
int32_t counter_x, // Counter variables for the bresenham line tracer
counter_y,
counter_z;
int32_t event_count; // Total event count. Retained for feed holds.
int32_t step_events_remaining; // Steps remaining in motion
// Used by Pramod Ranade inverse time algorithm
int32_t delta_d; // Ranade distance traveled per interrupt tick
int32_t d_counter; // Ranade distance traveled since last step event
uint8_t ramp_count; // Acceleration interrupt tick counter.
uint8_t ramp_type; // Ramp type variable.
uint8_t execute_step; // Flags step execution for each interrupt.
} stepper_t;
static stepper_t st;
static block_t *current_block; // A pointer to the block currently being traced
// Used by the stepper driver interrupt
static uint8_t step_pulse_time; // Step pulse reset time after step rise
static uint8_t out_bits; // The next stepping-bits to be output
// NOTE: If the main interrupt is guaranteed to be complete before the next interrupt, then
// this blocking variable is no longer needed. Only here for safety reasons.
static volatile uint8_t busy; // True when "Stepper Driver Interrupt" is being serviced. Used to avoid retriggering that handler.
// __________________________
// /| |\ _________________ ^
// / | | \ /| |\ |
// / | | \ / | | \ s
// / | | | | | \ p
// / | | | | | \ e
// +-----+------------------------+---+--+---------------+----+ e
// | BLOCK 1 | BLOCK 2 | d
//
// time ----->
//
// The trapezoid is the shape the speed curve over time. It starts at block->initial_rate, accelerates by block->rate_delta
// until reaching cruising speed block->nominal_rate, and/or until step_events_remaining reaches block->decelerate_after
// after which it decelerates until the block is completed. The driver uses constant acceleration, which is applied as
// +/- block->rate_delta velocity increments by the midpoint rule at each ACCELERATION_TICKS_PER_SECOND.
// Stepper state initialization. Cycle should only start if the st.cycle_start flag is
// enabled. Startup init and limits call this function but shouldn't start the cycle.
void st_wake_up()
{
// Enable steppers by resetting the stepper disable port
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) {
STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT);
} else {
STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
}
if (sys.state == STATE_CYCLE) {
// Initialize stepper output bits
out_bits = settings.invert_mask;
// Initialize step pulse timing from settings.
step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3);
// Enable stepper driver interrupt
st.execute_step = false;
TCNT2 = 0; // Clear Timer2
TIMSK2 |= (1<<OCIE2A); // Enable Timer2 Compare Match A interrupt
TCCR2B = (1<<CS21); // Begin Timer2. Full speed, 1/8 prescaler
}
}
// Stepper shutdown
void st_go_idle()
{
// Disable stepper driver interrupt. Allow Timer0 to finish. It will disable itself.
TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt
TCCR2B = 0; // Disable Timer2
busy = false;
// Disable steppers only upon system alarm activated or by user setting to not be kept enabled.
if ((settings.stepper_idle_lock_time != 0xff) || bit_istrue(sys.execute,EXEC_ALARM)) {
// Force stepper dwell to lock axes for a defined amount of time to ensure the axes come to a complete
// stop and not drift from residual inertial forces at the end of the last movement.
delay_ms(settings.stepper_idle_lock_time);
if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) {
STEPPERS_DISABLE_PORT &= ~(1<<STEPPERS_DISABLE_BIT);
} else {
STEPPERS_DISABLE_PORT |= (1<<STEPPERS_DISABLE_BIT);
}
}
}
// "The Stepper Driver Interrupt" - This timer interrupt is the workhorse of Grbl. It is based
// on the Pramod Ranade inverse time stepper algorithm, where a timer ticks at a constant
// frequency and uses time-distance counters to track when its the approximate time for any
// step event. However, the Ranade algorithm, as described, is susceptible to numerical round-off,
// meaning that some axes steps may not execute for a given multi-axis motion.
// Grbl's algorithm slightly differs by using a single Ranade time-distance counter to manage
// a Bresenham line algorithm for multi-axis step events which ensures the number of steps for
// each axis are executed exactly. In other words, it uses a Bresenham within a Bresenham algorithm,
// where one tracks time(Ranade) and the other steps.
// This interrupt pops blocks from the block_buffer and executes them by pulsing the stepper pins
// appropriately. It is supported by The Stepper Port Reset Interrupt which it uses to reset the
// stepper port after each pulse. The bresenham line tracer algorithm controls all three stepper
// outputs simultaneously with these two interrupts.
//
// NOTE: Average time in this ISR is: 5 usec iterating timers only, 20-25 usec with step event, or
// 15 usec when popping a block. So, ensure Ranade frequency and step pulse times work with this.
ISR(TIMER2_COMPA_vect)
{
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT; // Debug: Used to time ISR
if (busy) { return; } // The busy-flag is used to avoid reentering this interrupt
// Pulse stepper port pins, if flagged. New block dir will always be set one timer tick
// before any step pulse due to algorithm design.
if (st.execute_step) {
st.execute_step = false;
STEPPING_PORT = ( STEPPING_PORT & ~(DIRECTION_MASK | STEP_MASK) ) | out_bits;
TCNT0 = step_pulse_time; // Reload Timer0 counter.
TCCR0B = (1<<CS21); // Begin Timer0. Full speed, 1/8 prescaler
}
busy = true;
sei(); // Re-enable interrupts. This ISR will still finish before returning to main program.
// If there is no current block, attempt to pop one from the buffer
if (current_block == NULL) {
// Anything in the buffer? If so, initialize next motion.
current_block = plan_get_current_block();
if (current_block != NULL) {
// By algorithm design, the loading of the next block never coincides with a step event,
// since there is always one Ranade timer tick before a step event occurs. This means
// that the Bresenham counter math never is performed at the same time as the loading
// of a block, hence helping minimize total time spent in this interrupt.
// Initialize direction bits for block
out_bits = current_block->direction_bits ^ settings.invert_mask;
st.execute_step = true; // Set flag to set direction bits.
// Initialize Bresenham variables
st.counter_x = (current_block->step_event_count >> 1);
st.counter_y = st.counter_x;
st.counter_z = st.counter_x;
st.event_count = current_block->step_event_count;
st.step_events_remaining = st.event_count;
// During feed hold, do not update Ranade counter, rate, or ramp type. Keep decelerating.
if (sys.state == STATE_CYCLE) {
// Initialize Ranade variables
st.d_counter = current_block->d_next;
st.delta_d = current_block->initial_rate;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
// Initialize ramp type.
if (st.step_events_remaining == current_block->decelerate_after) { st.ramp_type = DECEL_RAMP; }
else if (st.delta_d == current_block->nominal_rate) { st.ramp_type = CRUISE_RAMP; }
else { st.ramp_type = ACCEL_RAMP; }
}
} else {
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP); // Flag main program for cycle end
return; // Nothing to do but exit.
}
}
// Adjust inverse time counter for ac/de-celerations
if (st.ramp_type) {
// Tick acceleration ramp counter
st.ramp_count--;
if (st.ramp_count == 0) {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK; // Reload ramp counter
if (st.ramp_type == ACCEL_RAMP) { // Adjust velocity for acceleration
st.delta_d += current_block->rate_delta;
if (st.delta_d >= current_block->nominal_rate) { // Reached cruise state.
st.ramp_type = CRUISE_RAMP;
st.delta_d = current_block->nominal_rate; // Set cruise velocity
}
} else if (st.ramp_type == DECEL_RAMP) { // Adjust velocity for deceleration
if (st.delta_d > current_block->rate_delta) {
st.delta_d -= current_block->rate_delta;
} else {
st.delta_d >>= 1; // Integer divide by 2 until complete. Also prevents overflow.
}
}
}
}
// Iterate Pramod Ranade inverse time counter. Triggers each Bresenham step event.
if (st.delta_d < MINIMUM_STEP_RATE) { st.d_counter -= MINIMUM_STEP_RATE; }
else { st.d_counter -= st.delta_d; }
// Execute Bresenham step event, when it's time to do so.
if (st.d_counter < 0) {
st.d_counter += current_block->d_next;
// Check for feed hold state and execute accordingly.
if (sys.state == STATE_HOLD) {
if (st.ramp_type != DECEL_RAMP) {
st.ramp_type = DECEL_RAMP;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
}
if (st.delta_d <= current_block->rate_delta) {
st_go_idle();
bit_true(sys.execute,EXEC_CYCLE_STOP);
return;
}
}
// TODO: Vary Bresenham resolution for smoother motions or enable faster step rates (>20kHz).
out_bits = current_block->direction_bits; // Reset out_bits and reload direction bits
st.execute_step = true;
// Execute step displacement profile by Bresenham line algorithm
st.counter_x -= current_block->steps[X_AXIS];
if (st.counter_x < 0) {
out_bits |= (1<<X_STEP_BIT);
st.counter_x += st.event_count;
if (out_bits & (1<<X_DIRECTION_BIT)) { sys.position[X_AXIS]--; }
else { sys.position[X_AXIS]++; }
}
st.counter_y -= current_block->steps[Y_AXIS];
if (st.counter_y < 0) {
out_bits |= (1<<Y_STEP_BIT);
st.counter_y += st.event_count;
if (out_bits & (1<<Y_DIRECTION_BIT)) { sys.position[Y_AXIS]--; }
else { sys.position[Y_AXIS]++; }
}
st.counter_z -= current_block->steps[Z_AXIS];
if (st.counter_z < 0) {
out_bits |= (1<<Z_STEP_BIT);
st.counter_z += st.event_count;
if (out_bits & (1<<Z_DIRECTION_BIT)) { sys.position[Z_AXIS]--; }
else { sys.position[Z_AXIS]++; }
}
// Check step events for trapezoid change or end of block.
st.step_events_remaining--; // Decrement step events count
if (st.step_events_remaining) {
if (st.ramp_type != DECEL_RAMP) {
// Acceleration and cruise handled by ramping. Just check for deceleration.
if (st.step_events_remaining <= current_block->decelerate_after) {
st.ramp_type = DECEL_RAMP;
if (st.step_events_remaining == current_block->decelerate_after) {
if (st.delta_d == current_block->nominal_rate) {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2; // Set ramp counter for trapezoid
} else {
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK-st.ramp_count; // Set ramp counter for triangle
}
}
}
}
} else {
// If current block is finished, reset pointer
current_block = NULL;
plan_discard_current_block();
}
out_bits ^= settings.invert_mask; // Apply step port invert mask
}
busy = false;
// SPINDLE_ENABLE_PORT ^= 1<<SPINDLE_ENABLE_BIT;
}
// 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.
ISR(TIMER0_OVF_vect)
{
STEPPING_PORT = (STEPPING_PORT & ~STEP_MASK) | (settings.invert_mask & STEP_MASK);
TCCR0B = 0; // Disable timer until needed.
}
// Reset and clear stepper subsystem variables
void st_reset()
{
memset(&st, 0, sizeof(st));
current_block = NULL;
busy = false;
}
// Initialize and start the stepper motor subsystem
void st_init()
{
// Configure directions of interface pins
STEPPING_DDR |= STEPPING_MASK;
STEPPING_PORT = (STEPPING_PORT & ~STEPPING_MASK) | settings.invert_mask;
STEPPERS_DISABLE_DDR |= 1<<STEPPERS_DISABLE_BIT;
// Configure Timer 2
TIMSK2 &= ~(1<<OCIE2A); // Disable Timer2 interrupt while configuring it
TCCR2B = 0; // Disable Timer2 until needed
TCNT2 = 0; // Clear Timer2 counter
TCCR2A = (1<<WGM21); // Set CTC mode
OCR2A = (F_CPU/ISR_TICKS_PER_SECOND)/8 - 1; // Set Timer2 CTC rate
// Configure Timer 0
TIMSK0 &= ~(1<<TOIE0);
TCCR0A = 0; // Normal operation
TCCR0B = 0; // Disable Timer0 until needed
TIMSK0 |= (1<<TOIE0); // Enable overflow interrupt
// Start in the idle state, but first wake up to check for keep steppers enabled option.
st_wake_up();
st_go_idle();
}
// Planner external interface to start stepper interrupt and execute the blocks in queue. Called
// by the main program functions: planner auto-start and run-time command execution.
void st_cycle_start()
{
if (sys.state == STATE_QUEUED) {
sys.state = STATE_CYCLE;
st_wake_up();
}
}
// Execute a feed hold with deceleration, only during cycle. Called by main program.
void st_feed_hold()
{
if (sys.state == STATE_CYCLE) {
sys.state = STATE_HOLD;
sys.auto_start = false; // Disable planner auto start upon feed hold.
}
}
// Reinitializes the cycle plan and stepper system after a feed hold for a resume. Called by
// runtime command execution in the main program, ensuring that the planner re-plans safely.
// NOTE: Bresenham algorithm variables are still maintained through both the planner and stepper
// cycle reinitializations. The stepper path should continue exactly as if nothing has happened.
// Only the planner de/ac-celerations profiles and stepper rates have been updated.
void st_cycle_reinitialize()
{
if (current_block != NULL) {
// Replan buffer from the feed hold stop location.
plan_cycle_reinitialize(st.step_events_remaining);
st.ramp_type = ACCEL_RAMP;
st.ramp_count = ISR_TICKS_PER_ACCELERATION_TICK/2;
st.delta_d = 0;
sys.state = STATE_QUEUED;
} else {
sys.state = STATE_IDLE;
}
}