diff --git a/COPYING b/COPYING index cdf88d6..1cc1c47 100644 --- a/COPYING +++ b/COPYING @@ -4,7 +4,7 @@ COPYRIGHT NOTICE FOR GRBL: Grbl - Embedded CNC g-code interpreter and motion-controller -Copyright (c) 2011-2015 Sungeun K. Jeon +Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Copyright (c) 2011 Jens Geisler @@ -28,27 +28,27 @@ COPYRIGHT NOTICE(S) FOR WORK CONTAINED IN THIS SOFTWARE: Copyright (c) 2008, Atmel Corporation All rights reserved. -Redistribution and use in source and binary forms, with or without +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -3. The name of ATMEL may not be used to endorse or promote products derived +3. The name of ATMEL may not be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED +THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY AND -SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY AND +SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY -OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile index be87bfe..839ec06 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Part of Grbl # # Copyright (c) 2009-2011 Simen Svale Skogsrud -# Copyright (c) 2012-2015 Sungeun K. Jeon +# Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC # # Grbl is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -32,7 +32,7 @@ DEVICE ?= atmega328p CLOCK = 16000000 PROGRAMMER ?= -c avrisp2 -P usb SOURCE = main.c motion_control.c gcode.c spindle_control.c coolant_control.c serial.c \ - protocol.c stepper.c eeprom.c settings.c planner.c nuts_bolts.c limits.c \ + protocol.c stepper.c eeprom.c settings.c planner.c nuts_bolts.c limits.c jog.c\ print.c probe.c report.c system.c BUILDDIR = build SOURCEDIR = grbl @@ -42,7 +42,7 @@ FUSES = -U hfuse:w:0xd2:m -U lfuse:w:0xff:m # Tune the lines below only if you know what you are doing: AVRDUDE = avrdude $(PROGRAMMER) -p $(DEVICE) -B 10 -F -COMPILE = avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE) -I. -ffunction-sections -fdata-sections +COMPILE = avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE) -I. -ffunction-sections OBJECTS = $(addprefix $(BUILDDIR)/,$(notdir $(SOURCE:.c=.o))) diff --git a/README.md b/README.md index 4d14cdf..c71d3e1 100644 --- a/README.md +++ b/README.md @@ -3,25 +3,25 @@ *** -_**This is the development branch for Grbl v1.0's upcoming release. In general, the new features here are beta, so use with caution. If you'd like to help, please report any bugs or oddities that you find! Thanks!**_ +_**This is the development branch for Grbl v1.0's upcoming release. Please keep in mind, the new features here are beta, so use with caution. If you'd like to help, please report any bugs or oddities that you find! Thanks!**_ *** -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. +Grbl is a no-compromise, high performance, low cost alternative to parallel-port-based motion control for CNC milling. This version of Grbl runs on an Arduino Uno. 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 maintain up to 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, all other primary g-code commands. Macro functions, variables, and most canned cycles are not supported, but we think GUIs can do a much better job at translating them into straight g-code anyhow. -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. +Grbl includes full acceleration management with look ahead. That means the controller will look up to 16 motions into the future and plan its velocities ahead to deliver smooth acceleration and jerk-free cornering. -* [Licensing](https://github.com/grbl/grbl/wiki/Licensing): Grbl is free software, released under the GPLv3 license. +* [Licensing](https://github.com/gnea/grbl/wiki/Licensing): Grbl is free software, released under the GPLv3 license. -* For more information and help, check out our **[Wiki pages!](https://github.com/grbl/grbl/wiki)** If you find that the information is out-dated, please to help us keep it updated by editing it or notifying our community! Thanks! +* For more information and help, check out our **[Wiki pages!](https://github.com/gnea/grbl/wiki)** If you find that the information is out-dated, please to help us keep it updated by editing it or notifying our community! Thanks! -* Lead Developer [_2011 - Current_]: Sungeun(Sonny) K. Jeon, Ph.D. (USA) aka @chamnit +* Lead Developer [_2011 - Current_]: Sungeun "Sonny" Jeon, Ph.D. (USA) aka @chamnit -* Lead Developer [_2009 - 2011_]: Simen Svale Skogsrud (Norway). aka The Originator/Creator/Pioneer/Father of Grbl. +* This work is built on the wonderful Grbl v0.6 firmware in 2011 written by Simen Svale Skogsrud (Norway). *** @@ -31,11 +31,9 @@ Grbl includes full acceleration management with look ahead. That means the contr *** -##Update Summary for v1.0c +##Update Summary for v1.0 - **IMPORTANT:** Your EEPROM will be wiped and restored with new settings. This is due to the addition of two new spindle speed '$' settings. -- Altered limit pin status reports from `Lim:000` to `Pin:000|0|0000`, where the `|` delimiters separate the new probe state and control pin states. Each new field may be disabled by the `$10` Grbl setting. NOTE: Commenting `REPORT_ALL_PIN_STATES` in config.h reverts to old `Lim:` reports, if needed. - - New safety door parking motion as a compile-option. Grbl will retract, disable the spindle/coolant, and park near Z max. When resumed, it will perform these task in reverse order and continue the program. Highly configurable. See config.h for details. - New '$' Grbl settings for max and min spindle rpm. Allows for tweaking the PWM output to more closely match true spindle rpm. When max rpm is set to zero or less than min rpm, the PWM pin D11 will act like a simple enable on/off output. @@ -44,12 +42,14 @@ Grbl includes full acceleration management with look ahead. That means the contr - **NOTE:** Arduino Mega2560 support has been moved to an active, official Grbl-Mega [project](http://www.github.com/gnea/grbl-Mega/). All new developments here and there will be synced when it makes sense to. +- Single file configuration for custom firmware. + - A few bug fixes and lots of refactoring to make the code more efficient and flexible. - -``` +``` List of Supported G-Codes in Grbl v0.9 Master: - Non-Modal Commands: G4, G10L2, G10L20, G28, G30, G28.1, G30.1, G53, G92, G92.1 - Motion Modes: G0, G1, G2, G3, G38.2, G38.3, G38.4, G38.5, G80 diff --git a/doc/csv/alarm_codes.csv b/doc/csv/alarm_codes.csv new file mode 100644 index 0000000..4020d7a --- /dev/null +++ b/doc/csv/alarm_codes.csv @@ -0,0 +1,9 @@ +1,Hard limit,Hard limit has been triggered. Machine position is likely lost due to sudden stop. Re-homing is highly recommended. +2,Soft limit,G-code motion target exceeds machine travel. Machine position safely retained. Alarm may be unlocked. +3,Abort during cycle,Reset while in motion. Grbl cannot guarantee position. Lost steps are likely. Re-homing is highly recommended. +4,Probe fail,If probe is not in the expected initial state before starting probe cycle when G38.2 and G38.3 is not triggered and G38.4 and G38.5 is triggered. +5,Probe fail,If the probe fails to contact the workpiece within the programmed travel for G38.2 and G38.4. +6,Homing fail,If the active homing cycle was reset. +7,Homing fail,If the safety door was opened during homing cycle. +8,Homing fail,Pull off travel failed to clear limit switch. Try increasing pull-off setting or check wiring. +9,Homing fail,Failed to find limit switch within travel. Defined as `1.5 * max_travel` on search and `5 * pulloff` on locate phases. \ No newline at end of file diff --git a/doc/csv/error_codes.csv b/doc/csv/error_codes.csv new file mode 100644 index 0000000..f8349eb --- /dev/null +++ b/doc/csv/error_codes.csv @@ -0,0 +1,34 @@ +1,Expected command letter,G-code words consist of a letter and a value. Letter was not found. +2,Bad number format,Numeric value format is not valid or missing an expected value. +3,Invalid statement,Grbl '$' system command was not recognized or supported. +4,Value < 0`,Negative value received for an expected positive value. +5,Setting disabled,Homing cycle is not enabled via settings. +6,Value < 3 usec,Minimum step pulse time must be greater than 3usec. +7,EEPROM read fail. Using defaults,EEPROM read failed. Reset and restored to default values. +8,Not idle,Grbl '$' command cannot be used unless Grbl is IDLE. Ensures smooth operation during a job. +9,G-code lock,G-code locked out during alarm or jog state. +10,Homing not enabled,Soft limits cannot be enabled without homing also enabled. +11,Line overflow,Max characters per line exceeded. Line was not processed and executed. +12,Step rate > 30kHz,Grbl '$' setting value exceeds the maximum step rate supported. +13,Check Door,Safety door detected as opened and door state initiated. +14,Line length exceeded,Build info or startup line exceeded EEPROM line length limit. +15,Travel exceeded,Jog target exceeds machine travel. Command ignored. +16,Invalid jog command,Jog command with no '=' or contains prohibited g-code. +20,Unsupported command,Unsupported or invalid g-code command found in block. +21,Modal group violation,More than one g-code command from same modal group found in block. +22,Undefined feed rate,Feed rate has not yet been set or is undefined. +23,Invalid gcode ID:23,G-code command in block requires an integer value. +24,Invalid gcode ID:24,More than one g-code command that requires axis words found in block. +25,Invalid gcode ID:25,Repeated g-code word found in block. +26,Invalid gcode ID:26,No axis words found in block for g-code command or mode which requires them. +27,Invalid gcode ID:27,Line number value is invalid. +28,Invalid gcode ID:28,G-code command is missing a required value word. +29,Invalid gcode ID:29,Work coordinate system commanded not supported. +30,Invalid gcode ID:30,G53 only allowed during G0 and G1 motion modes. +31,Invalid gcode ID:31,Axis words found in block while no command uses them. +32,Invalid gcode ID:32,G2/3 arcs require at least one in-plane axis word. +33,Invalid gcode ID:33,Motion command target is invalid. +34,Invalid gcode ID:34,Arc radius value is invalid. +35,Invalid gcode ID:35,G2/3 arcs require at least one in-plane offset word. +36,Invalid gcode ID:36,Unused value words found in block. +37,Invalid gcode ID:37,G43.1 dynamic tool length offset assigned to wrong axis. diff --git a/doc/markdown/error_codes.md b/doc/markdown/error_codes.md new file mode 100644 index 0000000..4dba4a1 --- /dev/null +++ b/doc/markdown/error_codes.md @@ -0,0 +1,124 @@ +## Meanings of Grbl messages and error/alarm codes + + +#### _'error:' Codes_ + +Format - `(v1.0)` `:` `(v0.9)` - `Description` + +- `error:1` : `error: Expected command letter` - G-code words consist of a letter and a value. Letter was not found. + +- `error:2` : `error: Bad number format` - Numeric value format is not valid or missing an expected value. + +- `error:3` : `error: Invalid statement` - Grbl '$' system command was not recognized or supported + +- `error:4` : `error: Value < 0` - Negative value received for an expected positive value. + +- `error:5` : `error: Setting disabled` - Homing cycle is not enabled via settings. + +- `error:6` : `error: Value < 3 usec` - Minimum step pulse time must be greater than 3usec + +- `error:7` : `error: EEPROM read fail. Using defaults` - EEPROM read failed. Reset and restored to default values. + +- `error:8` : `error: Not idle` - Grbl '$' command cannot be used unless Grbl is IDLE. Ensures smooth operation during a job. + +- `error:9` : `error: G-code lock` - G-code locked out during alarm or jog state + +- `error:10` : `error: Homing not enabled` - Soft limits cannot be enabled without homing also enabled. + +- `error:11` : `error: Line overflow` - Max characters per line exceeded. Line was not processed and executed. + +- `error:12` : `error: Step rate > 30kHz`* - Grbl '$' setting value exceeds the maximum step rate supported. + +- `error:13` : `error: Check Door` - Safety door detected as opened and door state initiated. + +- `error:14` : `error: Line length exceeded` - (Grbl-Mega Only) Build info or startup line exceeded EEPROM line length limit. + +- `error:15` : `error: Travel exceeded` - Jog target exceeds machine travel. Command ignored. + +- `error:16` : `error: Invalid jog command` - Jog command with no '=' or contains prohibited g-code. + +- `error:20` : `error: Unsupported command` - Unsupported or invalid g-code command found in block. + +- `error:21` : `error: Modal group violation` - More than one g-code command from same modal group found in block. + +- `error:22` : `error: Undefined feed rate` - Feed rate has not yet been set or is undefined. + +- `error:23` : `error: Invalid gcode ID:23` - G-code command in block requires an integer value. + +- `error:24` : `error: Invalid gcode ID:24` - More than one g-code command that requires axis words found in block. + +- `error:25` : `error: Invalid gcode ID:25` - Repeated g-code word found in block. + +- `error:26` : `error: Invalid gcode ID:26` - No axis words found in block for g-code command or mode which requires them. + +- `error:27` : `error: Invalid gcode ID:27` - Line number value is invalid + +- `error:28` : `error: Invalid gcode ID:28` - G-code command is missing a required value word. + +- `error:29` : `error: Invalid gcode ID:29` - Work coordinate system commanded not supported. + +- `error:30` : `error: Invalid gcode ID:30` - G53 only allowed during G0 and G1 motion modes. + +- `error:31` : `error: Invalid gcode ID:31` - Axis words found in block while no command uses them. + +- `error:32` : `error: Invalid gcode ID:32` - G2/3 arcs require at least one in-plane axis word. + +- `error:33` : `error: Invalid gcode ID:33` - Motion command target is invalid. + +- `error:34` : `error: Invalid gcode ID:34` - Arc radius value is invalid. + +- `error:35` : `error: Invalid gcode ID:35` - G2/3 arcs require at least one in-plane offset word. + +- `error:36` : `error: Invalid gcode ID:36` - Unused value words found in block. + +- `error:37` : `error: Invalid gcode ID:37` - G43.1 dynamic tool length offset assigned to wrong axis. + +`*` indicates feedback enabled only by compile-time option. + +----- + +#### 'Alarm:' Codes + +Format - `(v1.0)` `:` `(v0.9)` - `Description` + +- `ALARM:1` : `ALARM: Hard limit` - Hard limit has been triggered. Machine position is likely lost due to sudden stop. Re-homing is highly recommended. +` +- `ALARM:2` : `ALARM: Soft limit` - G-code motion target exceeds machine travel. Machine position safely retained. Alarm may be unlocked. + +- `ALARM:3` : `ALARM: Abort during cycle` - Reset while in motion. Grbl cannot guarantee position. Lost steps are likely. Re-homing is highly recommended. + +- `ALARM:4` : `ALARM: Probe fail` - If probe is not in the expected initial state before starting probe cycle, where G38.2 and G38.3 is not triggered and G38.4 and G38.5 is triggered. + +- `ALARM:5` : `ALARM: Probe fail` - If the probe fails to contact the workpiece within the programmed travel for G38.2 and G38.4. + +- `ALARM:6` : `ALARM: Homing fail` - If the active homing cycle was reset. + +- `ALARM:7` : `ALARM: Homing fail` - If the safety door was opened during homing cycle. + +- `ALARM:8` : `ALARM: Homing fail` - Pull off travel failed to clear limit switch. Try increasing pull-off setting or check wiring. + +- `ALARM:9` : `ALARM: Homing fail` - Failed to find limit switch within travel. Defined as `1.5 * max_travel` on search and `5 * pulloff` on locate phases. + +----- + +#### Message Descriptions + +Format - `Message` - `Description` + +- `[Reset to continue]` - Critical event message. Reset is required before Grbl accepts any other commands. This prevents ongoing command streaming and risking a motion before the alarm is acknowledged. Hard or soft limit errors will trigger this event. + +- `[‘$H’|’$X’ to unlock]`- Alarm message at initialization. All g-code commands and some ‘$’ are blocked until unlocked via homing or $X. + +- `[Caution: Unlocked]` - Alarm unlock $X acknowledgement. + +- `[Enabled]` - Indicates Grbl’s check-mode is enabled. + +- `[Disabled]` - Indicates Grbl’s check-mode is disabled. Grbl is automatically reset afterwards. + +- `[Check Door]` - Safety door detected as open. This message appears either immediately upon a safety door ajar or if the safety is open when Grbl initializes after a power-up/reset. + +- `[Check Limits]` - If Grbl detects a limit switch is triggered after power-up/reset and hard limits are enabled, this will appear as a courtesy message. + +- `[Pgm End]` - M2/30 program end message to denote g-code modes have been restored to defaults according to the M2/30 g-code description. + +- `[Restoring defaults]` - Acknowledgement message when restoring EEPROM defaults via a `$RST=` command. diff --git a/doc/markdown/jogging.md b/doc/markdown/jogging.md new file mode 100644 index 0000000..6c71826 --- /dev/null +++ b/doc/markdown/jogging.md @@ -0,0 +1,41 @@ +## Grbl v1.0 Jogging + +Executing a jog requires a specific command structure, as described below: + + - The first three characters must be '$J=' to indicate the jog. + - The jog command follows immediate after the '=' and works like a normal G1 command. + - Feed rate is only interpreted in G94 units per minute. A prior G93 state is ignored during jog. + - Required words: + - XYZ: One or more axis words with target value. + - F - Feed rate value. NOTE: Each jog requires this value and is not treated as modal. + - Optional words: Jog executes based on current G20/G21 and G90/G91 g-code parser state. If one + of the following optional words is passed, that state is overridden for one command only. + - G20 or G21 - Inch and millimeter mode + - G90 or G91 - Absolute and incremental distances + - G53 - Move in machine coordinates + - All other g-codes, m-codes, and value words are not accepted in the jog command. + - Spaces and comments are allowed in the command. These are removed by the pre-parser. + + - Example: G21 and G90 are active modal states prior to jogging. These are sequential commands. + - `$J=X10.0 Y-1.5` will move to X=10.0mm and Y=-1.5mm in work coordinate frame (WPos). + - `$J=G91 G20 X0.5` will move +0.5 inches (12.7mm) to X=22.7mm (WPos). Note that G91 and G20 are only applied to this jog command. + - `$J=G53 Y5.0` will move the machine to Y=5.0mm in the machine coordinate frame (MPos). If the work coordinate offset for the y-axis is 2.0mm, then Y is 3.0mm in (WPos). + +Jog commands behave almost identically to normal g-code streaming. Every jog command will +return an 'ok' when the jogging motion has been parsed and is setup for execution. If a +command is not valid, Grbl will return an 'error:'. Multiple jogging commands may be +queued in sequence. + +The main differences are: +- During a jog, Grbl will report a 'Jog' state while executing the jog. +- A jog command will only be accepted when Grbl is in either the 'Idle' or 'Jog' states. +- Jogging motions may not be mixed with g-code commands while executing, which will return + a lockout error, if attempted. +- All jogging motion(s) may be cancelled at anytime with a simple feed hold command. Grbl + will automatically flush Grbl's internal buffers of any queued jogging motions and return + to the 'Idle' state. No soft-reset required. +- IMPORTANT: Jogging does not alter the g-code parser state. Hence, no g-code modes need to + be explicitly managed, unlike previous ways of implementing jogs with commands like + 'G91G1X1F100'. Since G91, G1, and F feed rates are modal and if they are not changed + back prior to resuming/starting a job, a job may not run how its was intended and result + in a crash. diff --git a/doc/markdown/realtime_cmds.md b/doc/markdown/realtime_cmds.md new file mode 100644 index 0000000..9f918c1 --- /dev/null +++ b/doc/markdown/realtime_cmds.md @@ -0,0 +1,135 @@ +## Grbl v1.0 Realtime commands + +Realtime commands are single control characters that may be sent to Grbl to command and perform an action in real-time, regardless of what Grbl is doing at the time. These commands include a reset, feed hold, resume, status report query, and overrides (in v1.0). + +A realtime command: + +- Will execute within tens of milliseconds. + +- Is a single character that may be sent to Grbl at any time. + +- Does not require a line feed or carraige return after them. + +- Is not considered a part of the streaming protocol. + +- Will ignore multiple commands until it has executed the first received command. + +- May be tied to an input pin and may be operated with a button or switch. + +- Actions depends on state or what Grbl is doing. It may not do anything. + +- Descriptions explain how they work and what to expect. + +#### ASCII Realtime Command Descriptions +The normal ASCII realtime command characters used in Grbl v0.9 have been retained in Grbl v1.0 and are described below for completeness. + +- `0x18` (ctrl-x) : Soft-Reset + + - Immediately halts and resets Grbl. + - Accepts and executes this command at any time. + - If reset while in motion, Grbl will throw an alarm to indicate position may be lost from the motion halt. + - If reset while in not motion, position is retained and re-homing is not required. + - An input pin is available to connect a button or switch. + + +- `?` : Status Report Query + + - Immediately generates and sends back runtime data with a status report. + - Accepts and executes this command at any time, except during a homing cycle and when critical alarm (hard/soft limit error) is thrown. + + +- `~` : Cycle Start / Resume + + - Resumes a feed hold, a safety door/parking state when the door is closed, and the M0 program pause states. + - Command is otherwise ignored. + - If the parking compile-time option is enabled and the safety door state is ready to resume, Grbl will re-enable the spindle and coolant, move back into position, and then resume. + - An input pin is available to connect a button or switch. + + +- `!` : Feed Hold + + - Places Grbl into a suspend or HOLD state. If in motion, the machine will decelerate to a stop and then be suspended. + - Command executes when Grbl is in an IDLE, RUN, or JOG state. It is otherwise ignored. + - By machine control definition, a feed hold does not disable the spindle or coolant. Only motion. + - An input pin is available to connect a button or switch. + + +#### Extended-ASCII Realtime Command Descriptions + +Grbl v1.0 installed more than a dozen new realtime commands to control feed, rapid, and spindle overrides. To help prevent users from inadvertently altering overrides with a keystroke and allow for more commands later on, all of the new control characters have been moved to the extended ASCII character set. These are not readily type-able on a keyboard, but, depending on the OS, they may be entered using specific keystroke and code. GUI developers will need to be able to send extended ASCII characters, values `128 (0x80)` to `255 (0xFF)`, to Grbl to take advantage of these new features. + +- `0x84` : Safety Door + + - Although typically connected to an input pin to detect the opening of a safety door, this command allows a GUI to enact the safety door behavior with this command. + - Immediately suspends into a DOOR state and disables the spindle and coolant. If in motion, the machine will decelerate to a stop and then be suspended. + - If executed during homing, Grbl will instead halt motion and throw a homing alarm. + - If already in a suspend state or HOLD, the DOOR state supersedes it. + - If the parking compile-time option is enabled, Grbl will park the spindle to a specified location. + - Command executes when Grbl is in an IDLE, HOLD, RUN, HOMING, or JOG state. It is otherwise ignored. + - An input pin is available to connect a button or switch, if enabled with a compile-time option. + - Some builds of Grbl v0.9 used the `@` character for this command, but it was undocumented. Moved to extended-ASCII to prevent accidental commanding. + + +- Feed Overrides + + - Immediately alters the feed override value. An active feed motion is altered within tens of milliseconds. + - Does not alter rapid rates, which include G0, G28, and G30, or jog motions. + - Feed override value can not be 1% or greater than 200% + - If feed override value does not change, the command is ignored. + - Feed override range and increments may be changed in config.h. + - The commands are: + - `0x90` : Set 100% of programmed rate. + - `0x91` : Increase 10% + - `0x92` : Decrease 10% + - `0x93` : Increase 1% + - `0x94` : Decrease 1% + + +- Rapid Overrides + + - Immediately alters the rapid override value. An active rapid motion is altered within tens of milliseconds. + - Only effects rapid motions, which include G0, G28, and G30. + - If rapid override value does not change, the command is ignored. + - Rapid override set values may be changed in config.h. + - The commands are: + - `0x95` : Set to 100% full rapid rate. + - `0x96` : Set to 50% of rapid rate. + - `0x97` : Set to 25% of rapid rate. + + +- Spindle Speed Overrides + + - Immediately alters the spindle speed override value. An active spindle speed is altered within tens of milliseconds. + - Override values may be changed at any time, regardless of if the spindle is enabled or disabled. + - Spindle override value can not be 50% or greater than 200% + - If spindle override value does not change, the command is ignored. + - Spindle override range and increments may be altered in config.h. + - The commands are: + - `0x99` : Set 100% of programmed spindle speed + - `0x9A` : Increase 10% + - `0x9B` : Decrease 10% + - `0x9C` : Increase 1% + - `0x9D` : Decrease 1% + + + - `0x9E` : Toggle Spindle Stop + - Toggles spindle enable or disable state immediately, but only while in the HOLD. + - The command is otherwise ignored, especially while in motion. This prevents accidental disabling during a job that can either destroy the part/machine or personal injury. Industrial machines handle the spindle stop override similarly. + - When motion restarts via cycle start, the last spindle state will be restored and wait 4.0 seconds (configurable) before resuming the tool path. This ensures the user doesn't forget to turn it back on. + - While disabled, spindle speed override values may still be altered and will be in effect once the spindle is re-enabled. + - If a safety door is opened, the DOOR state will supercede the spindle stop override, where it will manage the spindle re-energizing itself upon closing the door and resuming. The prior spindle stop override state is cleared and reset. + + + - `0xA0` : Toggle Flood Coolant + - Toggles flood coolant state and output pin until the next toggle or g-code command alters it. + - May be commanded at any time while in IDLE, RUN, or HOLD states. It is otherwise ignored. + - This override directly changes the coolant modal state in the g-code parser. Grbl will continue to operate normally like it received and executed an `M8` or `M9` g-code command. + - When `$G` g-code parser state is queried, the toggle override change will be reflected by an `M8` enabled or disabled with an `M9` or not appearing when `M7` is present. + + + - `0xA1` : Toggle Mist Coolant + - Enabled by `ENABLE_M7` compile-time option. Default is disabled. + - Toggles mist coolant state and output pin until the next toggle or g-code command alters it. + - May be commanded at any time while in IDLE, RUN, or HOLD states. It is otherwise ignored. + - This override directly changes the coolant modal state in the g-code parser. Grbl will continue to operate normally like it received and executed an `M7` or `M9` g-code command. + - When `$G` g-code parser state is queried, the toggle override change will be reflected by an `M7` enabled or disabled with an `M9` or not appearing when `M8` is present. diff --git a/doc/markdown/report.md b/doc/markdown/report.md new file mode 100644 index 0000000..b5fd27d --- /dev/null +++ b/doc/markdown/report.md @@ -0,0 +1,279 @@ +### _Grbl v1.0 Realtime Status Reports_ (Rev. 2) + +-------- + +#### Summary of Changes from Grbl v0.9 Reports + +- Intent of changes is to make parsing cleaner, reduce transmitting overhead without effecting overall Grbl performance, and add more feedback data, which includes three new override values and real-time velocity. + +- Data fields are separated by `|` pipe delimiters, rather than `,` commas that were used to separate data values. This should help with parsing. + +- The ability to mask and add/remove data fields from status reports via the `$10` status report mask setting has been disabled. Only selecting `MPos:` or `WPos:` coordinates is allowed. + - All available data is always sent to standardize the reports across all GUIs. + - For unique situations, data fields can be removed by config.h macros, but it is highly recommended to not alter these. + + +- `MPos:` OR `WPos:` are always included in a report, but not BOTH at the same time. + + - This reduces transmit overhead tremendously by removing upwards to 40 characters. + - `WCO:0.000,10.000,2.500` A current work coordinate offset is now sent to easily convert between position vectors, where `WPos = MPos - WCO` for each axis. + - `WCO:` is included immediately whenever a `WCO:` value changes or intermittently after every **X** status reports as a refresh. Refresh rates can dynamically vary from 10 to 30 (configurable) reports depending on what Grbl is doing. + - `WCO:` is simply the sum of the work coordinate system, G92, and G43.1 tool length offsets. + - Basically, a GUI just needs to retain the last `WCO:` and apply the equation to get the other position vector. + - `WCO:` messages may only be disabled via a config.h compile-option, if a GUI wants to handle the work position calculations on its own to free up more transmit bandwidth. + - Be aware of the following issue regarding `WPos:`. + - In Grbl v0.9 and prior, there is an old outstanding bug where the `WPos:` work position reported may not correlate to what is executing, because `WPos:` is based on the g-code parser state, which can be several motions behind. Grbl v1.0 now forces the planner buffer to empty, sync, and stops motion whenever there is a command that alters the work coordinate offsets `G10,G43.1,G92,G54-59`. This is the simplest way to ensure `WPos:` is always correct. Fortunately, it's exceedingly rare that any of these commands are used need continuous motions through them. + - A compile-time option is available to disable the planner sync and forced stop, but, if used, it's up to the GUI to handle this position correlation issue. + + +- The `Hold` and `Door` states includes useful sub-state info via a `:` colon delimiter and an integer value. See descriptions for details. + +- Limit and other input pin reports have significantly changed to reduce transmit overhead. + - The data type description is now just `Pn:`, rather than `Lim:000` or `Pin:000|0|0000` + - It does not appear if no inputs are detected as triggered. + - If an input is triggered, ```Pn:``` will be followed by a letter or set of letters of every triggered input pin. `XYZPDHRS` for the XYZ-axes limits, Probe, Door, Hold, soft-Reset, cycle Start pins, respectively. + - For example, a triggered Z-limit and probe pin would report `Pn:ZP`. + + +- Buffer data (planner and serial RX) reports have been tweaked and combined. + + - `Bf:0,0`. The first value is planner blocks in use and the second is RX bytes in use. + + +- Override reports are intermittent since they don't change often once set. + + - Overrides are included in every 10 or 20 status reports (configurable) depending on what Grbl is doing or, if an override value or toggle state changes, automatically in the next report. + - There are two override fields: + - `Ov:100,100,100` Organized as feed, rapid, and spindle speed overrides in percent. + - `T:SFM` with each letter `S`, `F`, and `M` are defined as spindle stop active, flood coolant toggled, and mist coolant toggled, respectively. + + + +- Line numbers, when enabled in config.h, are omitted when: + + - No line number is passed to Grbl in a block. + - Grbl is performing a system motion like homing, jogging, or parking. + - Grbl is executing g-code block that does not contain a motion, like `G20G54` or `G4P1` dwell. (NOTE: Looking to fixing this later.) + +------- + +#### Basic Characteristics + +- Contains real-time data of Grbl’s state, position, and other data required independently of the stream. + +- Categorized as a real-time message, where it is a separate message that should not be counted as part of the streaming protocol. It may appear at any given time. + +- A status report is initiated by sending Grbl a '?' character. + + - Like all real-time commands, the '?' character is intercepted and never enters the serial buffer. It's never a part of the stream and can be sent at any time. + + - Grbl will generate and transmit a report within ~5-20 milliseconds. + + - Every ’?’ command sent by a GUI is not guaranteed with a response. The following are the current scenarios when Grbl may not immediately or ignore a status report request. _NOTE: These may change in the future and will be documented here._ + + - If two or more '?' queries are sent before the first report is generated, the additional queries are ignored. + + - A soft-reset commanded clears the last status report query. + + - When Grbl throws a critical alarm from a limit violation. A soft-reset is required to resume operation. + + - During a homing cycle. + +#### Message Construction: + + - A message is a single line of ascii text, completed by a carriage return and line feed. + + - `< >` Chevrons uniquely enclose reports to indicate message type. + + - `|` Pipe delimiters separate data fields inside the report. + + - The first data field is an exception to the following data field rules. See 'Machine State' description for details. + + - All remaining data fields consist of a data type followed by a `:` colon delimiter and data values. `type:value(s)` + + - Data values are given either as as one or more pre-defined character codes to indicate certain states/conditions or as numeric values, which are separated by a `,` comma delimiter when more than one is present. Numeric values are also in a pre-defined order and units of measure. + + - The first (Machine State) and second (Current Position) data fields are always included in every report. + + - Assume any following data field may or may not exist and can be in any order. The `$10` status report mask setting can alter what data is present and certain data fields can be reported intermittently (see descriptions for details.) + + - The `$13` report inches settings alters the units of some data values. `$13=0` false indicates mm-mode, while `$13=1` true indicates inch-mode reporting. Keep note of this setting and which report values can be altered. + +- _Data Field Descriptions:_ + + - **Machine State:** + + - Valid states types: `Idle, Run, Hold, Jog, Alarm, Door, Check, Home` + + - Sub-states may be included via `:` a colon delimiter and numeric code. + + - Current sub-states are: + + - `Hold:0` Hold complete. Ready to resume. + + - `Hold:1` Hold in-progress. Reset will throw an alarm. + + - `Door:0` Door closed. Ready to resume. + + - `Door:1` Machine stopped. Door still ajar. Can't resume until closed. + + - `Door:2` Door opened. Hold (or parking retract) in-progress. Reset will throw an alarm. + + - `Door:3` Door closed and resuming. Restoring from park, if applicable. Reset will throw an alarm. + + - This data field is always present as the first field. + + - **Current Position:** + + - Depending on `$10` status report mask settings, position may be sent as either: + + - `MPos:0.000,-10.000,5.000` machine position or + + - `WPos:-2.500,0.000,11.000` work position + + - Three position values are given in the order of X, Y, and Z. A fourth position value may exist in later versions for the A-axis. + + - `$13` report inches user setting effects these values and is given as either mm or inches. + + - This data field is always present as the second field. + + - **Work Coordinate Offset:** + + - `WCO:0.000,1.551,5.664` is the current work coordinate offset of the g-code parser, which is the sum of the current work coordinate system, G92 offsets, and G43.1 tool length offset. + + - Machine position and work position are related by this simple equation per axis: `WPos = MPos - WCO` + + - Values are given in the order of the X,Y, and Z axes offsets. A fourth offset value may exist in later versions for the A-axis. + - `$13` report inches user setting effects these values and is given as either mm or inches. + + - `WCO:` values don't change often during a job once set and only requires intermittent refreshing. + + - This data field appears: + + - In every 10 or 30 (configurable 1-255) status reports, depending on if Grbl is in a motion state or not. + + - Immediately in the next report, if an offset value has changed. + + - In the first report after a reset/power-cycle. + + - This data field will not appear if: + + - It is disabled in the config.h file. No `$` mask setting available. + + - The refresh counter is in-between intermittent reports. + + - **Buffer State:** + + - `Bf:0,0`. The first value is planner blocks in use and the second is RX bytes in use. + + - This data field will not appear if: + + - It is disabled by the `$` status report mask setting. + + - **Line Number:** + + - `Ln:99999` indicates line 99999 is currently being executed. This differs from the `$G` line `N` value since the parser is usually queued few blocks behind execution. + + - Compile-time option only because of memory requirements. However, if a GUI passes indicator line numbers onto Grbl, it's very useful to determine when Grbl is executing them. + + - This data field will not appear if: + + - It is disabled in the config.h file. No `$` mask setting available. + + - The line number reporting not enabled in config.h. Different option to reporting data field. + + - No line number or `N0` is passed with the g-code block. + + - Grbl is homing, jogging, parking, or performing a system task/motion. + + - There is no motion in the g-code block like a `G4P1` dwell. (May be fixed in later versions.) + + - **Current Rate:** + + - `F:1000.` indicates current actual feed rate (speed) of the executing motion. Depending on machine max rate settings and acceleration, this value may not be the programmed rate. + + - Value units, either in mm/min or inches/min, is dependent on the `$` report inches user setting. + + - As a operational note, reported rate is typically 30-50 msec behind actual position reported. + + - This data field will not appear if: + + - It is disabled in the config.h file. No `$` mask setting available. + + - **Input Pin State:** + + - `Pn:XYZPDHRS` indicates which input pins Grbl has detected as 'triggered'. + + - Pin state is evaluated every time a status report is generated. All input pin inversions are appropriately applied to determine 'triggered' states. + + - Each letter of `XYZPDHRS` denotes a particular 'triggered' input pin. + + - `X Y Z` XYZ limit pins, respectively + + - `P` the probe pin. + + - `D H R S` the door, hold, soft-reset, and cycle-start pins, respectively. + + - Example: `Pn:PZ` indicates the probe and z-limit pins are 'triggered'. + + - Note: `A` may be added in later versions for an A-axis limit pin. + + - Assume input pin letters are presented in no particular order. + + - One or more 'triggered' pin letter(s) will always be present with a `Pn:` data field. + + - This data field will not appear if: + + - It is disabled in the config.h file. No `$` mask setting available. + + - No input pins are detected as triggered. + + - **Override Values:** + + - `Ov:100,100,100` indicates current override values in percent of programmed values for feed, rapids, and spindle speed, respectively. + + - Override values don't change often during a job once set and only requires intermittent refreshing. This data field appears: + + - After 10 or 20 (configurable 1-255) status reports, depending on is in a motion state or not. + + - If an override value has changed, this data field will appear immediately in the next report. However, if `WCO:` is present, this data field will be delayed one report. + + - In the second report after a reset/power-cycle. + + - This data field will not appear if: + + - It is disabled in the config.h file. No `$` mask setting available. + + - The override refresh counter is in-between intermittent reports. + + - `WCO:` exists in current report during refresh. Automatically set to try again on next report. + + - **Toggle Overrides:** + + - `T:SFM` indicates a toggle override is in effect or has been commanded. + + - Like the pin state field, each letter denotes a particular toggle override. + + - `S` indicates the spindle stop toggle override is in effect. It will appear as long as the spindle stop override is active. + + - `F` indicates the flood coolant toggle override was activated. It will only appear once after it has executed the coolant state change. + + - `M` indicates the mist coolant toggle override was activated, if mist coolant is enabled via config.h. It will only appear once after it has executed the coolant state change. + + - Assume toggle override letters are presented in no particular order. + + - One or more active toggle override letter(s) will always be present with a `T:` data field. + + - This data field appears: + + - If a toggle override is active or has recently executed and only when the override values field is also present (see override value field rules). + + - This data field will not appear if: + + - If no toggle override is active or has been executed. + + - It is disabled in the config.h file. No `$` mask setting available. + + - If override refresh counter is in-between intermittent reports. + + - `WCO:` exists in current report during refresh. Automatically set to try again on next report. diff --git a/grbl/config.h b/grbl/config.h index 5528229..9a40da4 100644 --- a/grbl/config.h +++ b/grbl/config.h @@ -2,7 +2,7 @@ config.h - compile time configuration Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -18,7 +18,7 @@ You should have received a copy of the GNU General Public License along with Grbl. If not, see . */ - + // This file contains compile-time configurations for Grbl's internal system. For the most part, // 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. @@ -38,19 +38,49 @@ #define CPU_MAP_ATMEGA328P // Arduino Uno CPU // Serial baud rate +// #define BAUD_RATE 230400 #define BAUD_RATE 115200 // Define realtime 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 -// that do not and must not exist in the streamed g-code program. ASCII control characters may be -// used, if they are available per user setup. Also, extended ASCII codes (>127), which are never in +// that do not and must not exist in the streamed g-code program. ASCII control characters may be +// used, if they are available per user setup. Also, extended ASCII codes (>127), which are never in // g-code programs, maybe selected for interface programs. // NOTE: If changed, manually update help message in report.c. -#define CMD_STATUS_REPORT '?' -#define CMD_FEED_HOLD '!' -#define CMD_CYCLE_START '~' + #define CMD_RESET 0x18 // ctrl-x. -#define CMD_SAFETY_DOOR '@' +#define CMD_STATUS_REPORT '?' +#define CMD_CYCLE_START '~' +#define CMD_FEED_HOLD '!' +// #define CMD_SAFETY_DOOR '@' // Moved to extended ASCII. + +// NOTE: All override realtime commands must be in the extended ASCII character set, starting +// at character value 128 (0x80) and up to 255 (0xFF). If the normal set of realtime commands, +// such as status reports, feed hold, reset, and cycle start, are moved to the extended set +// space, serial.c's RX ISR will need to be modified to accomodate the change. +// #define CMD_RESET 0x80 +// #define CMD_STATUS_REPORT 0x81 +// #define CMD_CYCLE_START 0x82 +// #define CMD_FEED_HOLD 0x83 +#define CMD_SAFETY_DOOR 0x84 +#define CMD_DEBUG_REPORT 0x85 // Only when DEBUG enabled, sends debug report in '{}' braces. +#define CMD_FEED_OVR_RESET 0x90 // Restores feed override value to 100%. +#define CMD_FEED_OVR_COARSE_PLUS 0x91 +#define CMD_FEED_OVR_COARSE_MINUS 0x92 +#define CMD_FEED_OVR_FINE_PLUS 0x93 +#define CMD_FEED_OVR_FINE_MINUS 0x94 +#define CMD_RAPID_OVR_RESET 0x95 // Restores rapid override value to 100%. +#define CMD_RAPID_OVR_MEDIUM 0x96 +#define CMD_RAPID_OVR_LOW 0x97 +// #define CMD_RAPID_OVR_EXTRA_LOW 0x98 // *NOT SUPPORTED* +#define CMD_SPINDLE_OVR_RESET 0x99 // Restores spindle override value to 100%. +#define CMD_SPINDLE_OVR_COARSE_PLUS 0x9A +#define CMD_SPINDLE_OVR_COARSE_MINUS 0x9B +#define CMD_SPINDLE_OVR_FINE_PLUS 0x9C +#define CMD_SPINDLE_OVR_FINE_MINUS 0x9D +#define CMD_SPINDLE_OVR_STOP 0x9E +#define CMD_COOLANT_FLOOD_OVR_TOGGLE 0xA0 +#define CMD_COOLANT_MIST_OVR_TOGGLE 0xA1 // If homing is enabled, homing init lock sets Grbl into an alarm state upon power up. This forces // the user to perform the homing cycle (or override the locks) before doing anything else. This is @@ -59,17 +89,17 @@ // Define the homing cycle patterns with bitmasks. The homing cycle first performs a search mode // to quickly engage the limit switches, followed by a slower locate mode, and finished by a short -// pull-off motion to disengage the limit switches. The following HOMING_CYCLE_x defines are executed +// pull-off motion to disengage the limit switches. The following HOMING_CYCLE_x defines are executed // in order starting with suffix 0 and completes the homing routine for the specified-axes only. If // an axis is omitted from the defines, it will not home, nor will the system update its position. // Meaning that this allows for users with non-standard cartesian machines, such as a lathe (x then z, -// with no y), to configure the homing cycle behavior to their needs. +// with no y), to configure the homing cycle behavior to their needs. // NOTE: The homing cycle is designed to allow sharing of limit pins, if the axes are not in the same // cycle, but this requires some pin settings changes in cpu_map.h file. For example, the default homing // cycle can share the Z limit pin with either X or Y limit pins, since they are on different cycles. // By sharing a pin, this frees up a precious IO pin for other purposes. In theory, all axes limit pins // may be reduced to one pin, if all axes are homed with seperate cycles, or vice versa, all three axes -// on separate pin, but homed in one cycle. Also, it should be noted that the function of hard limits +// on separate pin, but homed in one cycle. Also, it should be noted that the function of hard limits // will not be affected by pin sharing. // NOTE: Defaults are set for a traditional 3-axis CNC machine. Z-axis first to clear, followed by X & Y. #define HOMING_CYCLE_0 (1< 3us, and, when added with the // user-supplied step pulse time, the total time must not exceed 127us. Reported successful @@ -324,62 +416,65 @@ // #define STEP_PULSE_DELAY 10 // Step pulse delay in microseconds. Default disabled. // 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 -// 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 -// up with planning new incoming motions as they are executed. -// #define BLOCK_BUFFER_SIZE 18 // Uncomment to override default in planner.h. +// 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 Mega2560. Or decrease if the Arduino 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. +// #define BLOCK_BUFFER_SIZE 16 // Uncomment to override default in planner.h. // Governs the size of the intermediary step segment buffer between the step execution algorithm // and the planner blocks. Each segment is set of steps executed at a constant velocity over a // fixed time defined by ACCELERATION_TICKS_PER_SECOND. They are computed such that the planner -// block velocity profile is traced exactly. The size of this buffer governs how much step -// execution lead time there is for other Grbl processes have to compute and do their thing +// block velocity profile is traced exactly. The size of this buffer governs how much step +// execution lead time there is for other Grbl processes have to compute and do their thing // before having to come back and refill this buffer, currently at ~50msec of step moves. // #define SEGMENT_BUFFER_SIZE 6 // Uncomment to override default in stepper.h. -// Line buffer size from the serial input stream to be executed. Also, governs the size of +// Line buffer size from the serial input stream to be executed. Also, governs the size of // each of the startup blocks, as they are each stored as a string of this size. Make sure // to account for the available EEPROM at the defined memory address in settings.h and for // the number of desired startup blocks. -// NOTE: 80 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 -// support up to 256 characters. In future versions, this default will be increased, when +// NOTE: 80 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 +// 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. // #define LINE_BUFFER_SIZE 80 // Uncomment to override default in protocol.h - + // 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 -// interfaces will character count and track each block send to each block response. So, +// interfaces will character count and track each block send to each block response. So, // increase the receive buffer if a deeper receive buffer is needed for streaming and avaiable // memory allows. The send buffer primarily handles messages in Grbl. Only increase if large // messages are sent and Grbl begins to stall, waiting to send the rest of the message. -// NOTE: Buffer size values must be greater than zero and less than 256. -// #define RX_BUFFER_SIZE 128 // Uncomment to override defaults in serial.h -// #define TX_BUFFER_SIZE 64 - +// NOTE: Grbl generates an average status report in about 0.5msec, but the serial TX stream at +// 115200 baud will take 5 msec to transmit a typical 55 character report. Worst case reports are +// around 90-100 characters. As long as the serial TX buffer doesn't get continually maxed, Grbl +// will continue operating efficiently. Size the TX buffer around the size of a worst-case report. +// #define RX_BUFFER_SIZE 128 // (1-254) Uncomment to override defaults in serial.h +// #define TX_BUFFER_SIZE 90 // (1-254) + // Toggles XON/XOFF software flow control for serial communications. Not officially supported // due to problems involving the Atmega8U2 USB-to-serial chips on current Arduinos. The firmware -// on these chips do not support XON/XOFF flow control characters and the intermediate buffer -// in the chips cause latency and overflow problems with standard terminal programs. However, +// on these chips do not support XON/XOFF flow control characters and the intermediate buffer +// in the chips cause latency and overflow problems with standard terminal programs. However, // using specifically-programmed UI's to manage this latency problem has been confirmed to work. // As well as, older FTDI FT232RL-based Arduinos(Duemilanove) are known to work with standard // terminal programs since their firmware correctly manage these XON/XOFF characters. In any // case, please report any successes to grbl administrators! // #define ENABLE_XONXOFF // Default disabled. Uncomment to enable. -// A simple software debouncing feature for hard limit switches. When enabled, the interrupt -// monitoring the hard limit switch pins will enable the Arduino's watchdog timer to re-check -// the limit pin state after a delay of about 32msec. This can help with CNC machines with -// problematic false triggering of their hard limit switches, but it WILL NOT fix issues with +// A simple software debouncing feature for hard limit switches. When enabled, the interrupt +// monitoring the hard limit switch pins will enable the Arduino's watchdog timer to re-check +// the limit pin state after a delay of about 32msec. This can help with CNC machines with +// problematic false triggering of their hard limit switches, but it WILL NOT fix issues with // electrical interference on the signal cables from external sources. It's recommended to first -// use shielded signal cables with their shielding connected to ground (old USB/computer cables +// use shielded signal cables with their shielding connected to ground (old USB/computer cables // work well and are cheap to find) and wire in a low-pass circuit into each limit pin. // #define ENABLE_SOFTWARE_DEBOUNCE // Default disabled. Uncomment to enable. // Force Grbl to check the state of the hard limit switches when the processor detects a pin // change inside the hard limit ISR routine. By default, Grbl will trigger the hard limits -// alarm upon any pin change, since bouncing switches can cause a state check like this to +// alarm upon any pin change, since bouncing switches can cause a state check like this to // misread the pin. When hard limits are triggered, they should be 100% reliable, which is the // reason that this option is disabled by default. Only if your system/electronics can guarantee // that the switches don't bounce, we recommend enabling this option. This will help prevent @@ -388,7 +483,7 @@ // #define HARD_LIMIT_FORCE_STATE_CHECK // Default disabled. Uncomment to enable. // Adjusts homing cycle search and locate scalars. These are the multipliers used by Grbl's -// homing cycle to ensure the limit switches are engaged and cleared through each phase of +// homing cycle to ensure the limit switches are engaged and cleared through each phase of // the cycle. The search phase uses the axes max-travel setting times the SEARCH_SCALAR to // determine distance to look for the limit switch. Once found, the locate phase begins and // uses the homing pull-off distance setting times the LOCATE_SCALAR to pull-off and re-engage @@ -397,19 +492,62 @@ // #define HOMING_AXIS_SEARCH_SCALAR 1.5 // Uncomment to override defaults in limits.c. // #define HOMING_AXIS_LOCATE_SCALAR 10.0 // Uncomment to override defaults in limits.c. +// Enable the '$RST=*', '$RST=$', and '$RST=#' eeprom restore commands. There are cases where +// these commands may be undesirable. Simply comment the desired macro to disable it. +// NOTE: See SETTINGS_RESTORE_ALL macro for customizing the `$RST=*` command. +#define ENABLE_RESTORE_EEPROM_WIPE_ALL // '$RST=*' Default enabled. Comment to disable. +#define ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS // '$RST=$' Default enabled. Comment to disable. +#define ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS // '$RST=#' Default enabled. Comment to disable. + +// Defines the EEPROM data restored upon a settings version change and `$RST=*` command. Whenever the +// the settings or other EEPROM data structure changes between Grbl versions, Grbl will automatically +// wipe and restore the EEPROM. This macro controls what data is wiped and restored. This is useful +// particularily for OEMs that need to retain certain data. For example, the BUILD_INFO string can be +// written into the Arduino EEPROM via a seperate .INO sketch to contain product data. Altering this +// macro to not restore the build info EEPROM will ensure this data is retained after firmware upgrades. +// NOTE: Uncomment to override defaults in settings.h +// #define SETTINGS_RESTORE_ALL (SETTINGS_RESTORE_DEFAULTS | SETTINGS_RESTORE_PARAMETERS | SETTINGS_RESTORE_STARTUP_LINES | SETTINGS_RESTORE_BUILD_INFO) + +// Enable the '$I=(string)' build info write command. If disabled, any existing build info data must +// be placed into EEPROM via external means with a valid checksum value. This macro option is useful +// to prevent this data from being over-written by a user, when used to store OEM product data. +// NOTE: See the included grblWrite_BuildInfo.ino example file to write this string seperately. +#define ENABLE_BUILD_INFO_WRITE_COMMAND // '$I=' Default enabled. Comment to disable. + +// AVR processors require all interrupts to be disabled during an EEPROM write. This includes both +// the stepper ISRs and serial comm ISRs. In the event of a long EEPROM write, this ISR pause can +// cause active stepping to lose position and serial receive data to be lost. This configuration +// option forces the planner buffer to completely empty whenever the EEPROM is written to prevent +// any chance of lost steps. +// However, this doesn't prevent issues with lost serial RX data during an EEPROM write, especially +// if a GUI is premptively filling up the serial RX buffer simultaneously. It's highly advised for +// GUIs to flag these gcodes (G10,G28.1,G30.1) to always wait for an 'ok' after a block containing +// one of these commands before sending more data to eliminate this issue. +// NOTE: Most EEPROM write commands are implicitly blocked during a job (all '$' commands). However, +// coordinate set g-code commands (G10,G28/30.1) are not, since they are part of an active streaming +// job. At this time, this option only forces a planner buffer sync with these g-code commands. +#define FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE // Default enabled. Comment to disable. + +// In Grbl v0.9 and prior, there is an old outstanding bug where the `WPos:` work position reported +// may not correlate to what is executing, because `WPos:` is based on the g-code parser state, which +// can be several motions behind. This option forces the planner buffer to empty, sync, and stop +// motion whenever there is a command that alters the work coordinate offsets `G10,G43.1,G92,G54-59`. +// This is the simplest way to ensure `WPos:` is always correct. Fortunately, it's exceedingly rare +// that any of these commands are used need continuous motions through them. +#define FORCE_BUFFER_SYNC_DURING_WCO_CHANGE // Default enabled. Comment to disable. // Enables and configures parking motion methods upon a safety door state. Primarily for OEMs -// that desire this feature for their integrated machines. At the moment, Grbl assumes that +// that desire this feature for their integrated machines. At the moment, Grbl assumes that // the parking motion only involves one axis, although the parking implementation was written -// to be easily refactored for any number of motions on different axes by altering the parking -// source code. At this time, Grbl only supports parking one axis (typically the Z-axis) that +// to be easily refactored for any number of motions on different axes by altering the parking +// source code. At this time, Grbl only supports parking one axis (typically the Z-axis) that // moves in the positive direction upon retracting and negative direction upon restoring position. -// The motion executes with a slow pull-out retraction motion, power-down, and a fast park. -// Restoring to the resume position follows these set motions in reverse: fast restore to +// The motion executes with a slow pull-out retraction motion, power-down, and a fast park. +// Restoring to the resume position follows these set motions in reverse: fast restore to // pull-out position, power-up with a time-out, and plunge back to the original position at the // slower pull-out rate. -// NOTE: Still a work-in-progress. Machine coordinates must be in all negative space and -// does not work with HOMING_FORCE_SET_ORIGIN enabled. Parking motion also moves only in +// NOTE: Still a work-in-progress. Machine coordinates must be in all negative space and +// does not work with HOMING_FORCE_SET_ORIGIN enabled. Parking motion also moves only in // positive direction. // #define PARKING_ENABLE // Default disabled. Uncomment to enable @@ -445,7 +583,7 @@ /* --------------------------------------------------------------------------------------- OEM Single File Configuration Option - + Instructions: Paste the cpu_map and default setting definitions below without an enclosing #ifdef. Comment out the CPU_MAP_xxx and DEFAULT_xxx defines at the top of this file, and the compiler will ignore the contents of defaults.h and cpu_map.h and use the definitions diff --git a/grbl/coolant_control.c b/grbl/coolant_control.c index bf6e3ee..246aa04 100644 --- a/grbl/coolant_control.c +++ b/grbl/coolant_control.c @@ -2,7 +2,7 @@ coolant_control.c - coolant control methods Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,51 +16,58 @@ You should have received a copy of the GNU General Public License along with Grbl. If not, see . -*/ +*/ #include "grbl.h" void coolant_init() { - COOLANT_FLOOD_DDR |= (1 << COOLANT_FLOOD_BIT); + COOLANT_FLOOD_DDR |= (1 << COOLANT_FLOOD_BIT); // Configure as output pin #ifdef ENABLE_M7 COOLANT_MIST_DDR |= (1 << COOLANT_MIST_BIT); #endif - coolant_stop(); -} - - -void coolant_stop() -{ - COOLANT_FLOOD_PORT &= ~(1 << COOLANT_FLOOD_BIT); - #ifdef ENABLE_M7 - COOLANT_MIST_PORT &= ~(1 << COOLANT_MIST_BIT); - #endif + coolant_set_state(COOLANT_DISABLE); } void coolant_set_state(uint8_t mode) { - if (sys.abort) { return; } // Block during abort. - - if (mode == COOLANT_FLOOD_ENABLE) { - COOLANT_FLOOD_PORT |= (1 << COOLANT_FLOOD_BIT); - - #ifdef ENABLE_M7 - } else if (mode == COOLANT_MIST_ENABLE) { - COOLANT_MIST_PORT |= (1 << COOLANT_MIST_BIT); - #endif - + if (mode & COOLANT_FLOOD_ENABLE) { + #ifdef INVERT_COOLANT_FLOOD_PIN + COOLANT_FLOOD_PORT &= ~(1 << COOLANT_FLOOD_BIT); + #else + COOLANT_FLOOD_PORT |= (1 << COOLANT_FLOOD_BIT); + #endif } else { - coolant_stop(); + #ifdef INVERT_COOLANT_FLOOD_PIN + COOLANT_FLOOD_PORT |= (1 << COOLANT_FLOOD_BIT); + #else + COOLANT_FLOOD_PORT &= ~(1 << COOLANT_FLOOD_BIT); + #endif } + #ifdef ENABLE_M7 + if (mode & COOLANT_MIST_ENABLE) { + #ifdef INVERT_COOLANT_MIST_PIN + COOLANT_MIST_PORT &= ~(1 << COOLANT_MIST_BIT); + #else + COOLANT_MIST_PORT |= (1 << COOLANT_MIST_BIT); + #endif + } else { + #ifdef INVERT_COOLANT_MIST_PIN + COOLANT_MIST_PORT |= (1 << COOLANT_MIST_BIT); + #else + COOLANT_MIST_PORT &= ~(1 << COOLANT_MIST_BIT); + #endif + } + #endif } void coolant_run(uint8_t mode) { if (sys.state == STATE_CHECK_MODE) { return; } - protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program. + protocol_buffer_synchronize(); // Ensure coolant turns on when specified in program. + if (sys.abort) { return; } // Block during abort. coolant_set_state(mode); } diff --git a/grbl/coolant_control.h b/grbl/coolant_control.h index 7694a78..5c363b5 100644 --- a/grbl/coolant_control.h +++ b/grbl/coolant_control.h @@ -2,7 +2,7 @@ coolant_control.h - spindle control methods Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,12 +19,11 @@ */ #ifndef coolant_control_h -#define coolant_control_h +#define coolant_control_h void coolant_init(); -void coolant_stop(); void coolant_set_state(uint8_t mode); void coolant_run(uint8_t mode); -#endif \ No newline at end of file +#endif diff --git a/grbl/cpu_map.h b/grbl/cpu_map.h index 53382cb..8be34a6 100644 --- a/grbl/cpu_map.h +++ b/grbl/cpu_map.h @@ -2,7 +2,7 @@ cpu_map.h - CPU and pin mapping configuration file Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,7 +18,7 @@ along with Grbl. If not, see . */ -/* The cpu_map.h files serve as a central pin mapping selection file for different +/* The cpu_map.h files serve as a central pin mapping selection file for different processor types or alternative pin layouts. This version of Grbl officially supports only the Arduino Mega328p. */ @@ -55,28 +55,28 @@ #define STEPPERS_DISABLE_BIT 0 // Uno Digital Pin 8 #define STEPPERS_DISABLE_MASK (1< 7.8kHz (Used in v0.9) + // #define SPINDLE_TCCRB_INIT_MASK ((1< 1.96kHz + #define SPINDLE_TCCRB_INIT_MASK (1< 0.98kHz + + + // NOTE: On the 328p, these must be the same as the SPINDLE_ENABLE settings. + #define SPINDLE_PWM_DDR DDRB + #define SPINDLE_PWM_PORT PORTB + #define SPINDLE_PWM_BIT 3 // Uno Digital Pin 11 #endif -/* +/* #ifdef CPU_MAP_CUSTOM_PROC // For a custom pin map or different processor, copy and edit one of the available cpu // map files and modify it to your needs. Make sure the defined name is also changed in diff --git a/grbl/defaults.h b/grbl/defaults.h index 7e19743..52cd36c 100644 --- a/grbl/defaults.h +++ b/grbl/defaults.h @@ -2,7 +2,7 @@ defaults.h - defaults settings configuration file Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ */ /* The defaults.h file serves as a central default settings selector for different machine - types, from DIY CNC mills to CNC conversions of off-the-shelf machines. The settings + types, from DIY CNC mills to CNC conversions of off-the-shelf machines. The settings files listed here are supplied by users, so your results may vary. However, this should give you a good starting point as you get to know your machine and tweak the settings for your nefarious needs. @@ -47,7 +47,7 @@ #define DEFAULT_STEPPING_INVERT_MASK 0 #define DEFAULT_DIRECTION_INVERT_MASK 0 #define DEFAULT_STEPPER_IDLE_LOCK_TIME 25 // msec (0-254, 255 keeps steppers enabled) - #define DEFAULT_STATUS_REPORT_MASK ((BITFLAG_RT_STATUS_MACHINE_POSITION)|(BITFLAG_RT_STATUS_WORK_POSITION)) + #define DEFAULT_STATUS_REPORT_MASK 255 // All enabled #define DEFAULT_JUNCTION_DEVIATION 0.01 // mm #define DEFAULT_ARC_TOLERANCE 0.002 // mm #define DEFAULT_REPORT_INCHES 0 // false @@ -55,6 +55,8 @@ #define DEFAULT_INVERT_LIMIT_PINS 0 // false #define DEFAULT_SOFT_LIMIT_ENABLE 0 // false #define DEFAULT_HARD_LIMIT_ENABLE 0 // false + #define DEFAULT_INVERT_PROBE_PIN 0 // false + #define DEFAULT_LASER_MODE 0 // false #define DEFAULT_HOMING_ENABLE 0 // false #define DEFAULT_HOMING_DIR_MASK 0 // move positive dir #define DEFAULT_HOMING_FEED_RATE 25.0 // mm/min @@ -82,12 +84,12 @@ #define DEFAULT_Y_MAX_TRAVEL 125.0 // mm #define DEFAULT_Z_MAX_TRAVEL 170.0 // mm #define DEFAULT_SPINDLE_RPM_MAX 2800.0 // rpm - #define DEFAULT_SPINDLE_RPM_MIN 0.0 // rpm + #define DEFAULT_SPINDLE_RPM_MIN 0.0 // rpm #define DEFAULT_STEP_PULSE_MICROSECONDS 10 #define DEFAULT_STEPPING_INVERT_MASK 0 - #define DEFAULT_DIRECTION_INVERT_MASK ((1< +#include + +#define SERIAL_BAUD_RATE 115200 +#define LINE_LENGTH 80U // Grbl line length +#define BYTE_LOCATION 942U // Grbl build info EEPROM address. + + +// ----- CHANGE THIS LINE ----- + +char build_info_line[LINE_LENGTH] = "Testing123."; + +// ----------------------------- + + +uint8_t status = false; +int ledPin = 13; // LED connected to digital pin 13 + +void setup() { + Serial.begin(SERIAL_BAUD_RATE); + delay(500); + + uint32_t address = BYTE_LOCATION; + uint32_t size = LINE_LENGTH; + char *write_pointer = (char*)build_info_line; + uint8_t write_checksum = 0; + for (; size>0; size--) { + write_checksum = (write_checksum << 1) || (write_checksum >> 7); + write_checksum += *write_pointer; + EEPROM.put(address++, *(write_pointer++)); + } + EEPROM.put(address,write_checksum); + + Serial.print(F("-> Writing line to EEPROM: '")); + Serial.print(build_info_line); + Serial.print(F("'\n\r-> Write checksum: ")); + Serial.println(write_checksum,DEC); + + size = LINE_LENGTH; + address = BYTE_LOCATION; + uint8_t data = 0; + char read_line[LINE_LENGTH]; + char *read_pointer = (char*)read_line; + uint8_t read_checksum = 0; + uint8_t stored_checksum = 0; + for(; size > 0; size--) { + data = EEPROM.read(address++); + read_checksum = (read_checksum << 1) || (read_checksum >> 7); + read_checksum += data; + *(read_pointer++) = data; + } + stored_checksum = EEPROM.read(address); + + Serial.print(F("<- Reading line from EEPROM: '")); + Serial.print(read_line); + Serial.print("'\n\r<- Read checksum: "); + Serial.println(read_checksum,DEC); + + if ((read_checksum == write_checksum) && (read_checksum == stored_checksum)) { + status = true; + Serial.print(F("SUCCESS! All checksums match!\r\n")); + } else { + if (write_checksum != stored_checksum) { + Serial.println(F("ERROR! Write and stored EEPROM checksums don't match!")); + } else { + Serial.println(F("ERROR! Read and stored checksums don't match!")); + } + } + pinMode(ledPin, OUTPUT); // sets the digital pin as output +} + +void loop() { + // Blink to let user know EEPROM write status. + // Slow blink is 'ok'. Fast blink is an 'error'. + digitalWrite(ledPin, HIGH); // sets the LED on + if (status) { delay(1500); } // Slow blink + else { delay(100); } // Rapid blink + digitalWrite(ledPin, LOW); // sets the LED off + if (status) { delay(1500); } + else { delay(100); } +} + + diff --git a/grbl/examples/grblWrite_BuildInfo/license.txt b/grbl/examples/grblWrite_BuildInfo/license.txt new file mode 100644 index 0000000..0da8f39 --- /dev/null +++ b/grbl/examples/grblWrite_BuildInfo/license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Sungeun K. Jeon for Gnea Research LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/grbl/gcode.c b/grbl/gcode.c index 727e94b..08a64d9 100644 --- a/grbl/gcode.c +++ b/grbl/gcode.c @@ -2,7 +2,7 @@ gcode.c - rs274/ngc parser. Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -24,10 +24,10 @@ // NOTE: Max line number is defined by the g-code standard to be 99999. It seems to be an // arbitrary value, and some GUIs may require more. So we increased it based on a max safe // value when converting a float (7.2 digit precision)s to an integer. -#define MAX_LINE_NUMBER 9999999 +#define MAX_LINE_NUMBER 10000000 #define AXIS_COMMAND_NONE 0 -#define AXIS_COMMAND_NON_MODAL 1 +#define AXIS_COMMAND_NON_MODAL 1 #define AXIS_COMMAND_MOTION_MODE 2 #define AXIS_COMMAND_TOOL_LENGTH_OFFSET 3 // *Undefined but required @@ -38,26 +38,26 @@ parser_block_t gc_block; #define FAIL(status) return(status); -void gc_init() +void gc_init() { memset(&gc_state, 0, sizeof(parser_state_t)); - + // Load default G54 coordinate system. - if (!(settings_read_coord_data(gc_state.modal.coord_select,gc_state.coord_system))) { - report_status_message(STATUS_SETTING_READ_FAIL); - } + if (!(settings_read_coord_data(gc_state.modal.coord_select,gc_state.coord_system))) { + report_status_message(STATUS_SETTING_READ_FAIL); + } } // Sets g-code parser position in mm. Input in steps. Called by the system abort and hard // limit pull-off routines. -void gc_sync_position() +void gc_sync_position() { - system_convert_array_steps_to_mpos(gc_state.position,sys.position); + system_convert_array_steps_to_mpos(gc_state.position,sys_position); } -static uint8_t gc_check_same_position(float *pos_a, float *pos_b) +static uint8_t gc_check_same_position(float *pos_a, float *pos_b) { uint8_t idx; for (idx=0; idx 'Z')) { FAIL(STATUS_EXPECTED_COMMAND_LETTER); } // [Expected word letter] @@ -118,27 +130,27 @@ uint8_t gc_execute_line(char *line) if (!read_float(line, &char_counter, &value)) { FAIL(STATUS_BAD_NUMBER_FORMAT); } // [Expected word value] // Convert values to smaller uint8 significand and mantissa values for parsing this word. - // NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more + // NOTE: Mantissa is multiplied by 100 to catch non-integer command values. This is more // accurate than the NIST gcode requirement of x10 when used for commands, but not quite // accurate enough for value words that require integers to within 0.0001. This should be - // a good enough comprimise and catch most all non-integer errors. To make it compliant, + // a good enough comprimise and catch most all non-integer errors. To make it compliant, // we would simply need to change the mantissa to int16, but this add compiled flash space. - // Maybe update this later. + // Maybe update this later. int_value = trunc(value); mantissa = round(100*(value - int_value)); // Compute mantissa for Gxx.x commands. - // NOTE: Rounding must be used to catch small floating point errors. + // NOTE: Rounding must be used to catch small floating point errors. // Check if the g-code word is supported or errors due to modal group violations or has // been repeated in the g-code block. If ok, update the command or record its value. switch(letter) { - + /* 'G' and 'M' Command Words: Parse commands and check for modal group violations. NOTE: Modal group numbers are defined in Table 4 of NIST RS274-NGC v3, pg.20 */ - + case 'G': // Determine 'G' command and its modal group switch(int_value) { - case 10: case 28: case 30: case 92: + case 10: case 28: case 30: case 92: // Check for G10/28/30/92 being called with G0/1/2/3/38 on same block. // * G43.1 is also an axis command but is not explicitly defined this way. if (mantissa == 0) { // Ignore G28.1, G30.1, and G92.1 @@ -146,8 +158,8 @@ uint8_t gc_execute_line(char *line) axis_command = AXIS_COMMAND_NON_MODAL; } // No break. Continues to next line. - case 4: case 53: - word_bit = MODAL_GROUP_G0; + case 4: case 53: + word_bit = MODAL_GROUP_G0; switch(int_value) { case 4: gc_block.non_modal_command = NON_MODAL_DWELL; break; // G4 case 10: gc_block.non_modal_command = NON_MODAL_SET_COORDINATE_DATA; break; // G10 @@ -159,7 +171,7 @@ uint8_t gc_execute_line(char *line) } mantissa = 0; // Set to zero to indicate valid non-integer G command. break; - case 30: + case 30: switch(mantissa) { case 0: gc_block.non_modal_command = NON_MODAL_GO_HOME_1; break; // G30 case 10: gc_block.non_modal_command = NON_MODAL_SET_HOME_1; break; // G30.1 @@ -168,30 +180,30 @@ uint8_t gc_execute_line(char *line) mantissa = 0; // Set to zero to indicate valid non-integer G command. break; case 53: gc_block.non_modal_command = NON_MODAL_ABSOLUTE_OVERRIDE; break; // G53 - case 92: + case 92: switch(mantissa) { case 0: gc_block.non_modal_command = NON_MODAL_SET_COORDINATE_OFFSET; break; // G92 case 10: gc_block.non_modal_command = NON_MODAL_RESET_COORDINATE_OFFSET; break; // G92.1 default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G92.x command] } mantissa = 0; // Set to zero to indicate valid non-integer G command. - break; + break; } break; - case 0: case 1: case 2: case 3: case 38: + case 0: case 1: case 2: case 3: case 38: // Check for G0/1/2/3/38 being called with G10/28/30/92 on same block. // * G43.1 is also an axis command but is not explicitly defined this way. if (axis_command) { FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); } // [Axis word/command conflict] - axis_command = AXIS_COMMAND_MOTION_MODE; + axis_command = AXIS_COMMAND_MOTION_MODE; // No break. Continues to next line. - case 80: - word_bit = MODAL_GROUP_G1; + case 80: + word_bit = MODAL_GROUP_G1; switch(int_value) { case 0: gc_block.modal.motion = MOTION_MODE_SEEK; break; // G0 case 1: gc_block.modal.motion = MOTION_MODE_LINEAR; break; // G1 case 2: gc_block.modal.motion = MOTION_MODE_CW_ARC; break; // G2 case 3: gc_block.modal.motion = MOTION_MODE_CCW_ARC; break; // G3 - case 38: + case 38: switch(mantissa) { case 20: gc_block.modal.motion = MOTION_MODE_PROBE_TOWARD; break; // G38.2 case 30: gc_block.modal.motion = MOTION_MODE_PROBE_TOWARD_NO_ERROR; break; // G38.3 @@ -202,19 +214,19 @@ uint8_t gc_execute_line(char *line) mantissa = 0; // Set to zero to indicate valid non-integer G command. break; case 80: gc_block.modal.motion = MOTION_MODE_NONE; break; // G80 - } + } break; - case 17: case 18: case 19: - word_bit = MODAL_GROUP_G2; + case 17: case 18: case 19: + word_bit = MODAL_GROUP_G2; switch(int_value) { case 17: gc_block.modal.plane_select = PLANE_SELECT_XY; break; case 18: gc_block.modal.plane_select = PLANE_SELECT_ZX; break; case 19: gc_block.modal.plane_select = PLANE_SELECT_YZ; break; } break; - case 90: case 91: + case 90: case 91: if (mantissa == 0) { - word_bit = MODAL_GROUP_G3; + word_bit = MODAL_GROUP_G3; if (int_value == 90) { gc_block.modal.distance = DISTANCE_MODE_ABSOLUTE; } // G90 else { gc_block.modal.distance = DISTANCE_MODE_INCREMENTAL; } // G91 } else { @@ -224,13 +236,13 @@ uint8_t gc_execute_line(char *line) // Otherwise, arc IJK incremental mode is default. G91.1 does nothing. } break; - case 93: case 94: - word_bit = MODAL_GROUP_G5; + case 93: case 94: + word_bit = MODAL_GROUP_G5; if (int_value == 93) { gc_block.modal.feed_rate = FEED_RATE_MODE_INVERSE_TIME; } // G93 else { gc_block.modal.feed_rate = FEED_RATE_MODE_UNITS_PER_MIN; } // G94 break; - case 20: case 21: - word_bit = MODAL_GROUP_G6; + case 20: case 21: + word_bit = MODAL_GROUP_G6; if (int_value == 20) { gc_block.modal.units = UNITS_MODE_INCHES; } // G20 else { gc_block.modal.units = UNITS_MODE_MM; } // G21 break; @@ -244,17 +256,17 @@ uint8_t gc_execute_line(char *line) word_bit = MODAL_GROUP_G8; // NOTE: The NIST g-code standard vaguely states that when a tool length offset is changed, // there cannot be any axis motion or coordinate offsets updated. Meaning G43, G43.1, and G49 - // all are explicit axis commands, regardless if they require axis words or not. + // all are explicit axis commands, regardless if they require axis words or not. if (axis_command) { FAIL(STATUS_GCODE_AXIS_COMMAND_CONFLICT); } // [Axis word/command conflict] } axis_command = AXIS_COMMAND_TOOL_LENGTH_OFFSET; if (int_value == 49) { // G49 - gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_CANCEL; + gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_CANCEL; } else if (mantissa == 10) { // G43.1 gc_block.modal.tool_length = TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC; } else { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [Unsupported G43.x command] mantissa = 0; // Set to zero to indicate valid non-integer G command. break; - case 54: case 55: case 56: case 57: case 58: case 59: + case 54: case 55: case 56: case 57: case 58: case 59: // NOTE: G59.x are not supported. (But their int_values would be 60, 61, and 62.) word_bit = MODAL_GROUP_G12; gc_block.modal.coord_select = int_value-54; // Shift to array indexing. @@ -265,32 +277,32 @@ uint8_t gc_execute_line(char *line) // gc_block.modal.control = CONTROL_MODE_EXACT_PATH; // G61 break; default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported G command] - } + } if (mantissa > 0) { FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); } // [Unsupported or invalid Gxx.x command] // Check for more than one command per modal group violations in the current block // NOTE: Variable 'word_bit' is always assigned, if the command is valid. if ( bit_istrue(command_words,bit(word_bit)) ) { FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); } command_words |= bit(word_bit); break; - + case 'M': - + // Determine 'M' command and its modal group if (mantissa > 0) { FAIL(STATUS_GCODE_COMMAND_VALUE_NOT_INTEGER); } // [No Mxx.x commands] switch(int_value) { - case 0: case 1: case 2: case 30: - word_bit = MODAL_GROUP_M4; + case 0: case 1: case 2: case 30: + word_bit = MODAL_GROUP_M4; switch(int_value) { case 0: gc_block.modal.program_flow = PROGRAM_FLOW_PAUSED; break; // Program pause case 1: break; // Optional stop not supported. Ignore. - case 2: case 30: gc_block.modal.program_flow = PROGRAM_FLOW_COMPLETED; break; // Program end and reset + case 2: case 30: gc_block.modal.program_flow = PROGRAM_FLOW_COMPLETED; break; // Program end and reset } break; #ifndef USE_SPINDLE_DIR_AS_ENABLE_PIN - case 4: + case 4: #endif case 3: case 5: - word_bit = MODAL_GROUP_M7; + word_bit = MODAL_GROUP_M7; switch(int_value) { case 3: gc_block.modal.spindle = SPINDLE_ENABLE_CW; break; #ifndef USE_SPINDLE_DIR_AS_ENABLE_PIN @@ -298,32 +310,33 @@ uint8_t gc_execute_line(char *line) #endif case 5: gc_block.modal.spindle = SPINDLE_DISABLE; break; } - break; - #ifdef ENABLE_M7 - case 7: - #endif - case 8: case 9: - word_bit = MODAL_GROUP_M8; - switch(int_value) { - #ifdef ENABLE_M7 - case 7: gc_block.modal.coolant = COOLANT_MIST_ENABLE; break; - #endif + break; + #ifdef ENABLE_M7 + case 7: case 8: case 9: + #else + case 8: case 9: + #endif + word_bit = MODAL_GROUP_M8; + switch(int_value) { + #ifdef ENABLE_M7 + case 7: gc_block.modal.coolant = COOLANT_MIST_ENABLE; break; + #endif case 8: gc_block.modal.coolant = COOLANT_FLOOD_ENABLE; break; case 9: gc_block.modal.coolant = COOLANT_DISABLE; break; } break; default: FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); // [Unsupported M command] } - + // Check for more than one command per modal group violations in the current block // NOTE: Variable 'word_bit' is always assigned, if the command is valid. if ( bit_istrue(command_words,bit(word_bit)) ) { FAIL(STATUS_GCODE_MODAL_GROUP_VIOLATION); } command_words |= bit(word_bit); break; - + // NOTE: All remaining letters assign values. - default: - + default: + /* Non-Command Words: This initial parsing phase only checks for repeats of the remaining legal g-code words and stores their value. Error-checking is performed later since some words (I,J,K,L,P,R) have multiple connotations and/or depend on the issued commands. */ @@ -349,8 +362,8 @@ uint8_t gc_execute_line(char *line) case 'Y': word_bit = WORD_Y; gc_block.values.xyz[Y_AXIS] = value; axis_words |= (1< MAX_LINE_NUMBER) { FAIL(STATUS_GCODE_INVALID_LINE_NUMBER); } // [Exceeds max line number] } // bit_false(value_words,bit(WORD_N)); // NOTE: Single-meaning value word. Set at end of error-checking. - + // Track for unused words at the end of error-checking. // NOTE: Single-meaning value words are removed all at once at the end of error-checking, because // they are always used when present. This was done to save a few bytes of flash. For clarity, the // single-meaning value words may be removed as they are used. Also, axis words are treated in the - // same way. If there is an explicit/implicit axis command, XYZ words are always used and are - // are removed at the end of error-checking. - + // same way. If there is an explicit/implicit axis command, XYZ words are always used and are + // are removed at the end of error-checking. + // [1. Comments ]: MSG's NOT SUPPORTED. Comment handling performed by protocol. - + // [2. Set feed rate mode ]: G93 F word missing with G1,G2/3 active, implicitly or explicitly. Feed rate // is not defined after switching to G94 from G93. if (gc_block.modal.feed_rate == FEED_RATE_MODE_INVERSE_TIME) { // = G93 // NOTE: G38 can also operate in inverse time, but is undefined as an error. Missing F word check added here. - if (axis_command == AXIS_COMMAND_MOTION_MODE) { + if (axis_command == AXIS_COMMAND_MOTION_MODE) { if ((gc_block.modal.motion != MOTION_MODE_NONE) || (gc_block.modal.motion != MOTION_MODE_SEEK)) { if (bit_isfalse(value_words,bit(WORD_F))) { FAIL(STATUS_GCODE_UNDEFINED_FEED_RATE); } // [F word missing] } @@ -427,13 +440,13 @@ uint8_t gc_execute_line(char *line) // NOTE: It seems redundant to check for an F word to be passed after switching from G94 to G93. We would // accomplish the exact same thing if the feed rate value is always reset to zero and undefined after each // inverse time block, since the commands that use this value already perform undefined checks. This would - // also allow other commands, following this switch, to execute and not error out needlessly. This code is + // also allow other commands, following this switch, to execute and not error out needlessly. This code is // combined with the above feed rate mode and the below set feed rate error-checking. // [3. Set feed rate ]: F is negative (done.) // - In inverse time mode: Always implicitly zero the feed rate value before and after block completion. - // NOTE: If in G93 mode or switched into it from G94, just keep F value as initialized zero or passed F word - // value in the block. If no F word is passed with a motion command that requires a feed rate, this will error + // NOTE: If in G93 mode or switched into it from G94, just keep F value as initialized zero or passed F word + // value in the block. If no F word is passed with a motion command that requires a feed rate, this will error // out in the motion modes error-checking. However, if no F word is passed with NO motion command that requires // a feed rate, we simply move on and the state feed rate value gets updated to zero and remains undefined. } else { // = G94 @@ -442,16 +455,17 @@ uint8_t gc_execute_line(char *line) if (bit_istrue(value_words,bit(WORD_F))) { if (gc_block.modal.units == UNITS_MODE_INCHES) { gc_block.values.f *= MM_PER_INCH; } } else { - gc_block.values.f = gc_state.feed_rate; // Push last state feed rate + // NOTE: Jogging mode does not pass modal feed rate and requires unique values for each command. + if (!is_jog_motion) { gc_block.values.f = gc_state.feed_rate; } // Push last state feed rate } } // Else, switching to G94 from G93, so don't push last state feed rate. Its undefined or the passed F word value. - } + } // bit_false(value_words,bit(WORD_F)); // NOTE: Single-meaning value word. Set at end of error-checking. - + // [4. Set spindle speed ]: S is negative (done.) if (bit_isfalse(value_words,bit(WORD_S))) { gc_block.values.s = gc_state.spindle_speed; } // bit_false(value_words,bit(WORD_S)); // NOTE: Single-meaning value word. Set at end of error-checking. - + // [5. Select tool ]: NOT SUPPORTED. Only tracks value. T is negative (done.) Not an integer. Greater than max tool value. // bit_false(value_words,bit(WORD_T)); // NOTE: Single-meaning value word. Set at end of error-checking. @@ -459,13 +473,13 @@ uint8_t gc_execute_line(char *line) // [7. Spindle control ]: N/A // [8. Coolant control ]: N/A // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. - + // [10. Dwell ]: P value missing. P is negative (done.) NOTE: See below. if (gc_block.non_modal_command == NON_MODAL_DWELL) { if (bit_isfalse(value_words,bit(WORD_P))) { FAIL(STATUS_GCODE_VALUE_WORD_MISSING); } // [P word missing] bit_false(value_words,bit(WORD_P)); } - + // [11. Set active plane ]: N/A switch (gc_block.modal.plane_select) { case PLANE_SELECT_XY: @@ -482,8 +496,8 @@ uint8_t gc_execute_line(char *line) axis_0 = Y_AXIS; axis_1 = Z_AXIS; axis_linear = X_AXIS; - } - + } + // [12. Set length units ]: N/A // Pre-convert XYZ coordinate values to millimeters, if applicable. uint8_t idx; @@ -494,15 +508,15 @@ uint8_t gc_execute_line(char *line) } } } - + // [13. Cutter radius compensation ]: G41/42 NOT SUPPORTED. Error, if enabled while G53 is active. // [G40 Errors]: G2/3 arc is programmed after a G40. The linear move after disabling is less than tool diameter. - // NOTE: Since cutter radius compensation is never enabled, these G40 errors don't apply. Grbl supports G40 + // NOTE: Since cutter radius compensation is never enabled, these G40 errors don't apply. Grbl supports G40 // only for the purpose to not error when G40 is sent with a g-code program header to setup the default modes. - - // [14. Cutter length compensation ]: G43 NOT SUPPORTED, but G43.1 and G49 are. - // [G43.1 Errors]: Motion command in same line. - // NOTE: Although not explicitly stated so, G43.1 should be applied to only one valid + + // [14. Cutter length compensation ]: G43 NOT SUPPORTED, but G43.1 and G49 are. + // [G43.1 Errors]: Motion command in same line. + // NOTE: Although not explicitly stated so, G43.1 should be applied to only one valid // axis that is configured (in config.h). There should be an error if the configured axis // is absent or if any of the other axis words are present. if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET ) { // Indicates called in block. @@ -510,31 +524,31 @@ uint8_t gc_execute_line(char *line) if (axis_words ^ (1< N_COORDINATE_SYSTEM) { FAIL(STATUS_GCODE_UNSUPPORTED_COORD_SYS); } // [Greater than N sys] if (gc_state.modal.coord_select != gc_block.modal.coord_select) { - if (!(settings_read_coord_data(gc_block.modal.coord_select,coordinate_data))) { FAIL(STATUS_SETTING_READ_FAIL); } + if (!(settings_read_coord_data(gc_block.modal.coord_select,coordinate_data))) { FAIL(STATUS_SETTING_READ_FAIL); } } } - + // [16. Set path control mode ]: N/A. Only G61. G61.1 and G64 NOT SUPPORTED. // [17. Set distance mode ]: N/A. Only G91.1. G90.1 NOT SUPPORTED. // [18. Set retract mode ]: NOT SUPPORTED. - + // [19. Remaining non-modal actions ]: Check go to predefined position, set G10, or set axis offsets. // NOTE: We need to separate the non-modal commands that are axis word-using (G10/G28/G30/G92), as these // commands all treat axis words differently. G10 as absolute offsets or computes current position as // the axis value, G92 similarly to G10 L20, and G28/30 as an intermediate target position that observes - // all the current coordinate system and G92 offsets. + // all the current coordinate system and G92 offsets. switch (gc_block.non_modal_command) { - case NON_MODAL_SET_COORDINATE_DATA: + case NON_MODAL_SET_COORDINATE_DATA: // [G10 Errors]: L missing and is not 2 or 20. P word missing. (Negative P value done.) // [G10 L2 Errors]: R word NOT SUPPORTED. P value not 0 to nCoordSys(max 9). Axis words missing. // [G10 L20 Errors]: P must be 0 to nCoordSys(max 9). Axis words missing. @@ -548,12 +562,12 @@ uint8_t gc_execute_line(char *line) } else { FAIL(STATUS_GCODE_UNSUPPORTED_COMMAND); } // [Unsupported L] } bit_false(value_words,(bit(WORD_L)|bit(WORD_P))); - + // Determine coordinate system to change and try to load from EEPROM. if (coord_select > 0) { coord_select--; } // Adjust P1-P6 index to EEPROM coordinate data indexing. else { coord_select = gc_block.modal.coord_select; } // Index P0 as the active coordinate system if (!settings_read_coord_data(coord_select,parameter_data)) { FAIL(STATUS_SETTING_READ_FAIL); } // [EEPROM read fail] - + // Pre-calculate the coordinate data changes. NOTE: Uses parameter_data since coordinate_data may be in use by G54-59. for (idx=0; idx C -----------------+--------------- T <- [x,y] | <------ d/2 ---->| - + C - Current position T - Target position O - center of circle that pass through both C and T d - distance from C to T r - designated radius h - distance from center of CT to O - + Expanding the equations: - + d -> sqrt(x^2 + y^2) h -> sqrt(4 * r^2 - x^2 - y^2)/2 - i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2)) / sqrt(x^2 + y^2)) / 2 - + Which can be written: - + i -> (x - (y * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 j -> (y + (x * sqrt(4 * r^2 - x^2 - y^2))/sqrt(x^2 + y^2))/2 - + Which we for size and speed reasons optimize to: - + 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 - j = (y + (x * h_x2_div_d))/2 - */ + j = (y + (x * h_x2_div_d))/2 + */ // 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. float h_x2_div_d = 4.0 * gc_block.values.r*gc_block.values.r - x*x - y*y; if (h_x2_div_d < 0) { FAIL(STATUS_GCODE_ARC_RADIUS_ERROR); } // [Arc radius error] - + // Finish computing h_x2_div_d. h_x2_div_d = -sqrt(h_x2_div_d)/hypot_f(x,y); // == -(h * 2 / d) // Invert the sign of h_x2_div_d if the circle is counter clockwise (see sketch below) - if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) { h_x2_div_d = -h_x2_div_d; } + if (gc_block.modal.motion == MOTION_MODE_CCW_ARC) { h_x2_div_d = -h_x2_div_d; } /* The counter clockwise circle lies to the left of the target direction. When offset is positive, the left hand circle will be generated - when it is negative the right hand circle is generated. - + T <-- Target position - - ^ + + ^ Clockwise circles with this center | Clockwise circles with this center will have will have > 180 deg of angular travel | < 180 deg of angular travel, which is a good thing! - \ | / + \ | / center of arc when h_x2_div_d is positive -> x <----- | -----> x <- center of arc when h_x2_div_d is negative | | - - C <-- Current position - */ - // Negative R is g-code-alese for "I want a circle with more than 180 degrees of travel" (go figure!), - // even though it is advised against ever generating such circles in a single line of g-code. By + + C <-- Current position + */ + // Negative R is g-code-alese for "I want a circle with more than 180 degrees of travel" (go figure!), + // 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 // travel and thus we get the unadvisably long arcs as prescribed. - if (gc_block.values.r < 0) { - h_x2_div_d = -h_x2_div_d; + if (gc_block.values.r < 0) { + h_x2_div_d = -h_x2_div_d; gc_block.values.r = -gc_block.values.r; // Finished with r. Set to positive for mc_arc - } + } // Complete the operation by calculating the actual center of the arc gc_block.values.ijk[axis_0] = 0.5*(x-(y*h_x2_div_d)); gc_block.values.ijk[axis_1] = 0.5*(y+(x*h_x2_div_d)); - - } else { // Arc Center Format Offset Mode + + } else { // Arc Center Format Offset Mode if (!(ijk_words & (bit(axis_0)|bit(axis_1)))) { FAIL(STATUS_GCODE_NO_OFFSETS_IN_PLANE); } // [No offsets in plane] - bit_false(value_words,(bit(WORD_I)|bit(WORD_J)|bit(WORD_K))); - + bit_false(value_words,(bit(WORD_I)|bit(WORD_J)|bit(WORD_K))); + // Convert IJK values to proper units. if (gc_block.modal.units == UNITS_MODE_INCHES) { for (idx=0; idx 0.005) { + if (delta_r > 0.005) { if (delta_r > 0.5) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Arc definition error] > 0.5mm if (delta_r > (0.001*gc_block.values.r)) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Arc definition error] > 0.005mm AND 0.1% radius } @@ -819,48 +833,86 @@ uint8_t gc_execute_line(char *line) case MOTION_MODE_PROBE_AWAY: case MOTION_MODE_PROBE_AWAY_NO_ERROR: // [G38 Errors]: Target is same current. No axis words. Cutter compensation is enabled. Feed rate // is undefined. Probe is triggered. NOTE: Probe check moved to probe cycle. Instead of returning - // an error, it issues an alarm to prevent further motion to the probe. It's also done there to + // an error, it issues an alarm to prevent further motion to the probe. It's also done there to // allow the planner buffer to empty and move off the probe trigger before another probing cycle. if (!axis_words) { FAIL(STATUS_GCODE_NO_AXIS_WORDS); } // [No axis words] if (gc_check_same_position(gc_state.position, gc_block.values.xyz)) { FAIL(STATUS_GCODE_INVALID_TARGET); } // [Invalid target] break; - } + } } } - + // [21. Program flow ]: No error checks required. // [0. Non-specific error-checks]: Complete unused value words check, i.e. IJK used when in arc - // radius mode, or axis words that aren't used in the block. - bit_false(value_words,(bit(WORD_N)|bit(WORD_F)|bit(WORD_S)|bit(WORD_T))); // Remove single-meaning value words. - if (axis_command) { bit_false(value_words,(bit(WORD_X)|bit(WORD_Y)|bit(WORD_Z))); } // Remove axis words. + // radius mode, or axis words that aren't used in the block. + bit_false(value_words,(bit(WORD_N)|bit(WORD_F)|bit(WORD_S)|bit(WORD_T))); // Remove single-meaning value words. + if (axis_command) { bit_false(value_words,(bit(WORD_X)|bit(WORD_Y)|bit(WORD_Z))); } // Remove axis words. if (value_words) { FAIL(STATUS_GCODE_UNUSED_WORDS); } // [Unused words] - + + /* ------------------------------------------------------------------------------------- + STEP 3.5ish : EXECUTE JOG!! + Intercept jog commands and complete error checking for valid jog commands and execute. + NOTE: G-code parser state is not updated, except the position to ensure sequential jog + targets are computed correctly. The final parser position after a jog is updated in + protocol_execute_realtime() when jogging completes or is canceled. + */ + + if (is_jog_motion) { + // Only distance and unit modal commands and G53 absolute override command are allowed. + if (command_words & ~(bit(MODAL_GROUP_G3) | bit(MODAL_GROUP_G6 | bit(MODAL_GROUP_G0))) ) { FAIL(STATUS_INVALID_JOG_COMMAND) }; + if (!(gc_block.non_modal_command == NON_MODAL_ABSOLUTE_OVERRIDE || gc_block.non_modal_command == NON_MODAL_NO_ACTION)) { FAIL(STATUS_INVALID_JOG_COMMAND); } + // NOTE: Feed rate word and axis word checks have already been performed in STEP 3. + uint8_t status = jog_execute(&gc_block); + if (status == STATUS_OK) { memcpy(gc_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); } + return(status); + } + /* ------------------------------------------------------------------------------------- STEP 4: EXECUTE!! Assumes that all error-checking has been completed and no failure modes exist. We just need to update the state and execute the block according to the order-of-execution. - */ - - // [0. Non-specific/common error-checks and miscellaneous setup]: + */ + + // Initialize planner data struct for motion blocks. + plan_line_data_t plan_data; + plan_line_data_t *pl_data = &plan_data; + memset(pl_data,0,sizeof(plan_line_data_t)); // Zero pl_data struct + + // [0. Non-specific/common error-checks and miscellaneous setup]: + // NOTE: If no line number is present, the value is zero. gc_state.line_number = gc_block.values.n; - + #ifdef USE_LINE_NUMBERS + pl_data->line_number = gc_state.line_number; // Record data for planner use. + #endif + // [1. Comments feedback ]: NOT SUPPORTED - + // [2. Set feed rate mode ]: gc_state.modal.feed_rate = gc_block.modal.feed_rate; - + pl_data->condition |= gc_state.modal.feed_rate; // Set condition flag for planner use. + // [3. Set feed rate ]: gc_state.feed_rate = gc_block.values.f; // Always copy this value. See feed rate error-checking. + pl_data->feed_rate = gc_state.feed_rate; // Record data for planner use. // [4. Set spindle speed ]: - if (gc_state.spindle_speed != gc_block.values.s) { - // Update running spindle only if not in check mode and not already enabled. - if (gc_state.modal.spindle != SPINDLE_DISABLE) { spindle_run(gc_state.modal.spindle, gc_block.values.s); } - gc_state.spindle_speed = gc_block.values.s; + if (gc_state.spindle_speed != gc_block.values.s) { + #ifdef VARIABLE_SPINDLE + // Do not stop motion if in laser mode and a G1, G2, or G3 motion is being executed. + if ( !(bit_istrue(settings.flags,BITFLAG_LASER_MODE) && (axis_command == AXIS_COMMAND_MOTION_MODE) && + ((gc_block.modal.motion == MOTION_MODE_LINEAR ) || (gc_block.modal.motion == MOTION_MODE_CW_ARC) || (gc_block.modal.motion == MOTION_MODE_CCW_ARC)) ) ) { + // Update running spindle only if not in check mode and not already enabled. + if (gc_state.modal.spindle != SPINDLE_DISABLE) { spindle_run(gc_state.modal.spindle, gc_block.values.s); } + } + #else + if (gc_state.modal.spindle != SPINDLE_DISABLE) { spindle_run(gc_state.modal.spindle, gc_block.values.s); } + #endif + gc_state.spindle_speed = gc_block.values.s; } - + pl_data->spindle_speed = gc_state.spindle_speed; // Record data for planner use. + // [5. Select tool ]: NOT SUPPORTED. Only tracks tool value. gc_state.tool = gc_block.values.t; @@ -868,24 +920,29 @@ uint8_t gc_execute_line(char *line) // [7. Spindle control ]: if (gc_state.modal.spindle != gc_block.modal.spindle) { - // Update spindle control and apply spindle speed when enabling it in this block. + // Update spindle control and apply spindle speed when enabling it in this block. spindle_run(gc_block.modal.spindle, gc_state.spindle_speed); - gc_state.modal.spindle = gc_block.modal.spindle; + gc_state.modal.spindle = gc_block.modal.spindle; } + pl_data->condition |= gc_state.modal.spindle; // Set condition flag for planner use. - // [8. Coolant control ]: + // [8. Coolant control ]: if (gc_state.modal.coolant != gc_block.modal.coolant) { + // NOTE: Coolant M-codes are modal. Only one command per line is allowed. But, multiple states + // can exist at the same time, while coolant disable clears all states. coolant_run(gc_block.modal.coolant); - gc_state.modal.coolant = gc_block.modal.coolant; + if (gc_block.modal.coolant == COOLANT_DISABLE) { gc_state.modal.coolant = COOLANT_DISABLE; } + else { gc_state.modal.coolant |= gc_block.modal.coolant; } } - - // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED + pl_data->condition |= gc_state.modal.coolant; // Set condition flag for planner use. + + // [9. Enable/disable feed rate or spindle overrides ]: NOT SUPPORTED. Always enabled. // [10. Dwell ]: if (gc_block.non_modal_command == NON_MODAL_DWELL) { mc_dwell(gc_block.values.p); } - + // [11. Set active plane ]: - gc_state.modal.plane_select = gc_block.modal.plane_select; + gc_state.modal.plane_select = gc_block.modal.plane_select; // [12. Set length units ]: gc_state.modal.units = gc_block.modal.units; @@ -896,55 +953,52 @@ uint8_t gc_execute_line(char *line) // [14. Cutter length compensation ]: G43.1 and G49 supported. G43 NOT SUPPORTED. // NOTE: If G43 were supported, its operation wouldn't be any different from G43.1 in terms // of execution. The error-checking step would simply load the offset value into the correct - // axis of the block XYZ value array. + // axis of the block XYZ value array. if (axis_command == AXIS_COMMAND_TOOL_LENGTH_OFFSET ) { // Indicates a change. gc_state.modal.tool_length = gc_block.modal.tool_length; - if (gc_state.modal.tool_length == TOOL_LENGTH_OFFSET_ENABLE_DYNAMIC) { // G43.1 + if (gc_state.modal.tool_length == TOOL_LENGTH_OFFSET_CANCEL) { // G49 + gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS] = 0.0; + } // else G43.1 + if ( gc_state.tool_length_offset != gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS] ) { gc_state.tool_length_offset = gc_block.values.xyz[TOOL_LENGTH_OFFSET_AXIS]; - } else { // G49 - gc_state.tool_length_offset = 0.0; + system_flag_wco_change(); } } - + // [15. Coordinate system selection ]: if (gc_state.modal.coord_select != gc_block.modal.coord_select) { gc_state.modal.coord_select = gc_block.modal.coord_select; memcpy(gc_state.coord_system,coordinate_data,sizeof(coordinate_data)); + system_flag_wco_change(); } - + // [16. Set path control mode ]: G61.1/G64 NOT SUPPORTED // gc_state.modal.control = gc_block.modal.control; // NOTE: Always default. - + // [17. Set distance mode ]: gc_state.modal.distance = gc_block.modal.distance; - + // [18. Set retract mode ]: NOT SUPPORTED - + // [19. Go to predefined position, Set G10, or Set axis offsets ]: switch(gc_block.non_modal_command) { - case NON_MODAL_SET_COORDINATE_DATA: + case NON_MODAL_SET_COORDINATE_DATA: settings_write_coord_data(coord_select,parameter_data); // Update system coordinate system if currently active. - if (gc_state.modal.coord_select == coord_select) { memcpy(gc_state.coord_system,parameter_data,sizeof(parameter_data)); } - break; - case NON_MODAL_GO_HOME_0: case NON_MODAL_GO_HOME_1: - // Move to intermediate position before going home. Obeys current coordinate system and offsets - // and absolute and incremental modes. - if (axis_command) { - #ifdef USE_LINE_NUMBERS - mc_line(gc_block.values.xyz, -1.0, false, gc_state.line_number); - #else - mc_line(gc_block.values.xyz, -1.0, false); - #endif + if (gc_state.modal.coord_select == coord_select) { + memcpy(gc_state.coord_system,parameter_data,sizeof(parameter_data)); + system_flag_wco_change(); } - #ifdef USE_LINE_NUMBERS - mc_line(parameter_data, -1.0, false, gc_state.line_number); - #else - mc_line(parameter_data, -1.0, false); - #endif + break; + case NON_MODAL_GO_HOME_0: case NON_MODAL_GO_HOME_1: + // Move to intermediate position before going home. Obeys current coordinate system and offsets + // and absolute and incremental modes. + pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. + if (axis_command) { mc_line(gc_block.values.xyz, pl_data); } + mc_line(parameter_data, pl_data); memcpy(gc_state.position, parameter_data, sizeof(parameter_data)); break; - case NON_MODAL_SET_HOME_0: + case NON_MODAL_SET_HOME_0: settings_write_coord_data(SETTING_INDEX_G28,gc_state.position); break; case NON_MODAL_SET_HOME_1: @@ -952,95 +1006,64 @@ uint8_t gc_execute_line(char *line) break; case NON_MODAL_SET_COORDINATE_OFFSET: memcpy(gc_state.coord_offset,gc_block.values.xyz,sizeof(gc_block.values.xyz)); + system_flag_wco_change(); break; - case NON_MODAL_RESET_COORDINATE_OFFSET: + case NON_MODAL_RESET_COORDINATE_OFFSET: clear_vector(gc_state.coord_offset); // Disable G92 offsets by zeroing offset vector. + system_flag_wco_change(); break; } - + // [20. Motion modes ]: - // NOTE: Commands G10,G28,G30,G92 lock out and prevent axis words from use in motion modes. + // NOTE: Commands G10,G28,G30,G92 lock out and prevent axis words from use in motion modes. // Enter motion modes only if there are axis words or a motion mode command word in the block. gc_state.modal.motion = gc_block.modal.motion; if (gc_state.modal.motion != MOTION_MODE_NONE) { if (axis_command == AXIS_COMMAND_MOTION_MODE) { switch (gc_state.modal.motion) { case MOTION_MODE_SEEK: - #ifdef USE_LINE_NUMBERS - mc_line(gc_block.values.xyz, -1.0, false, gc_state.line_number); - #else - mc_line(gc_block.values.xyz, -1.0, false); - #endif + pl_data->condition |= PL_COND_FLAG_RAPID_MOTION; // Set rapid motion condition flag. + mc_line(gc_block.values.xyz, pl_data); break; case MOTION_MODE_LINEAR: - #ifdef USE_LINE_NUMBERS - mc_line(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, gc_state.line_number); - #else - mc_line(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate); - #endif + mc_line(gc_block.values.xyz, pl_data); + break; + case MOTION_MODE_CW_ARC: + mc_arc(gc_block.values.xyz, pl_data, gc_state.position, gc_block.values.ijk, gc_block.values.r, + axis_0, axis_1, axis_linear, true); break; - case MOTION_MODE_CW_ARC: - #ifdef USE_LINE_NUMBERS - mc_arc(gc_state.position, gc_block.values.xyz, gc_block.values.ijk, gc_block.values.r, - gc_state.feed_rate, gc_state.modal.feed_rate, axis_0, axis_1, axis_linear, true, gc_state.line_number); - #else - mc_arc(gc_state.position, gc_block.values.xyz, gc_block.values.ijk, gc_block.values.r, - gc_state.feed_rate, gc_state.modal.feed_rate, axis_0, axis_1, axis_linear, true); - #endif - break; case MOTION_MODE_CCW_ARC: - #ifdef USE_LINE_NUMBERS - mc_arc(gc_state.position, gc_block.values.xyz, gc_block.values.ijk, gc_block.values.r, - gc_state.feed_rate, gc_state.modal.feed_rate, axis_0, axis_1, axis_linear, false, gc_state.line_number); - #else - mc_arc(gc_state.position, gc_block.values.xyz, gc_block.values.ijk, gc_block.values.r, - gc_state.feed_rate, gc_state.modal.feed_rate, axis_0, axis_1, axis_linear, false); - #endif + mc_arc(gc_block.values.xyz, pl_data, gc_state.position, gc_block.values.ijk, gc_block.values.r, + axis_0, axis_1, axis_linear, false); break; - case MOTION_MODE_PROBE_TOWARD: + case MOTION_MODE_PROBE_TOWARD: // NOTE: gc_block.values.xyz is returned from mc_probe_cycle with the updated position value. So // upon a successful probing cycle, the machine position and the returned value should be the same. - #ifdef USE_LINE_NUMBERS - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, false, false, gc_state.line_number); - #else - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, false, false); - #endif + mc_probe_cycle(gc_block.values.xyz, pl_data, false, false); break; case MOTION_MODE_PROBE_TOWARD_NO_ERROR: - #ifdef USE_LINE_NUMBERS - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, false, true, gc_state.line_number); - #else - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, false, true); - #endif + mc_probe_cycle(gc_block.values.xyz, pl_data, false, true); break; case MOTION_MODE_PROBE_AWAY: - #ifdef USE_LINE_NUMBERS - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, true, false, gc_state.line_number); - #else - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, true, false); - #endif + mc_probe_cycle(gc_block.values.xyz, pl_data, true, false); break; case MOTION_MODE_PROBE_AWAY_NO_ERROR: - #ifdef USE_LINE_NUMBERS - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, true, true, gc_state.line_number); - #else - mc_probe_cycle(gc_block.values.xyz, gc_state.feed_rate, gc_state.modal.feed_rate, true, true); - #endif + mc_probe_cycle(gc_block.values.xyz, pl_data, true, true); } - + // 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_state.position, gc_block.values.xyz, sizeof(gc_block.values.xyz)); // gc_state.position[] = gc_block.values.xyz[] } } - + // [21. Program flow ]: - // M0,M1,M2,M30: Perform non-running program flow actions. During a program pause, the buffer may + // 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. gc_state.modal.program_flow = gc_block.modal.program_flow; - if (gc_state.modal.program_flow) { + if (gc_state.modal.program_flow) { protocol_buffer_synchronize(); // Sync and finish all remaining buffered motions before moving on. if (gc_state.modal.program_flow == PROGRAM_FLOW_PAUSED) { if (sys.state != STATE_CHECK_MODE) { @@ -1048,7 +1071,7 @@ uint8_t gc_execute_line(char *line) protocol_execute_realtime(); // Execute suspend. } } else { // == PROGRAM_FLOW_COMPLETED - // Upon program complete, only a subset of g-codes reset to certain defaults, according to + // Upon program complete, only a subset of g-codes reset to certain defaults, according to // LinuxCNC's program end descriptions and testing. Only modal groups [G-code 1,2,3,5,7,12] // and [M-code 7,8,9] reset to [G1,G17,G90,G94,G40,G54,M5,M9,M48]. The remaining modal groups // [G-code 4,6,8,10,13,14,15] and [M-code 4,5,6] and the modal words [F,S,T,H] do not reset. @@ -1061,26 +1084,33 @@ uint8_t gc_execute_line(char *line) gc_state.modal.spindle = SPINDLE_DISABLE; gc_state.modal.coolant = COOLANT_DISABLE; // gc_state.modal.override = OVERRIDE_DISABLE; // Not supported. - + + #ifdef RESTORE_OVERRIDES_AFTER_PROGRAM_END + sys.f_override = DEFAULT_FEED_OVERRIDE; + sys.r_override = DEFAULT_RAPID_OVERRIDE; + sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; + #endif + // Execute coordinate change and spindle/coolant stop. if (sys.state != STATE_CHECK_MODE) { - if (!(settings_read_coord_data(gc_state.modal.coord_select,coordinate_data))) { FAIL(STATUS_SETTING_READ_FAIL); } + if (!(settings_read_coord_data(gc_state.modal.coord_select,coordinate_data))) { FAIL(STATUS_SETTING_READ_FAIL); } memcpy(gc_state.coord_system,coordinate_data,sizeof(coordinate_data)); + system_flag_wco_change(); // Set to refresh immediately just in case something altered. spindle_stop(); - coolant_stop(); + coolant_set_state(COOLANT_DISABLE); } - report_feedback_message(MESSAGE_PROGRAM_END); } gc_state.modal.program_flow = PROGRAM_FLOW_RUNNING; // Reset program flow. } - + // TODO: % to denote start of program. + return(STATUS_OK); } - -/* + +/* Not supported: - Canned cycles @@ -1091,7 +1121,7 @@ uint8_t gc_execute_line(char *line) - Override control (TBD) - Tool changes - Switches - + (*) Indicates optional parameter, enabled through config.h and re-compile group 0 = {G92.2, G92.3} (Non modal: Cancel and re-enable G92 offsets) group 1 = {G81 - G89} (Motion modes: Canned cycles) @@ -1099,7 +1129,7 @@ uint8_t gc_execute_line(char *line) group 6 = {M6} (Tool change) group 7 = {G41, G42} cutter radius compensation (G40 is supported) group 8 = {G43} tool length offset (G43.1/G49 are supported) - group 8 = {*M7} enable mist coolant (* Compile-option) + group 8 = {M7*} enable mist coolant (* Compile-option) group 9 = {M48, M49} enable/disable feed and speed override switches group 10 = {G98, G99} return mode canned cycles group 13 = {G61.1, G64} path control mode (G61 is supported) diff --git a/grbl/gcode.h b/grbl/gcode.h index 9a289af..319fd9b 100644 --- a/grbl/gcode.h +++ b/grbl/gcode.h @@ -2,9 +2,9 @@ gcode.h - rs274/ngc parser. Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC 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 @@ -23,10 +23,10 @@ #define gcode_h -// Define modal group internal numbers for checking multiple command violations and tracking the +// Define modal group internal numbers for checking multiple command violations and tracking the // type of command that is called in the block. A modal group is a group of g-code commands that are // mutually exclusive, or cannot exist on the same line, because they each toggle a state or execute -// a unique motion. These are defined in the NIST RS274-NGC v3 g-code standard, available online, +// a unique motion. These are defined in the NIST RS274-NGC v3 g-code standard, available online, // and are similar/identical to other g-code interpreters by manufacturers (Haas,Fanuc,Mazak,etc). // NOTE: Modal group define values must be sequential and starting from zero. #define MODAL_GROUP_G0 0 // [G4,G10,G28,G28.1,G30,G30.1,G53,G92,G92.1] Non-modal @@ -93,8 +93,8 @@ #define PROGRAM_FLOW_COMPLETED 2 // M2, M30 // Modal Group G5: Feed rate mode -#define FEED_RATE_MODE_UNITS_PER_MIN 0 // G94 (Default: Must be zero) -#define FEED_RATE_MODE_INVERSE_TIME 1 // G93 +#define FEED_RATE_MODE_UNITS_PER_MIN 0 // G94 (Default: Must be zero) +#define FEED_RATE_MODE_INVERSE_TIME PL_COND_FLAG_INVERSE_TIME // G93 (NOTE: Uses planner condition bit flag) // Modal Group G6: Units mode #define UNITS_MODE_MM 0 // G21 (Default: Must be zero) @@ -108,13 +108,13 @@ // Modal Group M7: Spindle control #define SPINDLE_DISABLE 0 // M5 (Default: Must be zero) -#define SPINDLE_ENABLE_CW 1 // M3 -#define SPINDLE_ENABLE_CCW 2 // M4 +#define SPINDLE_ENABLE_CW PL_COND_FLAG_SPINDLE_CW // M3 (NOTE: Uses planner condition bit flag) +#define SPINDLE_ENABLE_CCW PL_COND_FLAG_SPINDLE_CCW // M4 (NOTE: Uses planner condition bit flag) // Modal Group M8: Coolant control #define COOLANT_DISABLE 0 // M9 (Default: Must be zero) -#define COOLANT_MIST_ENABLE 1 // M7 -#define COOLANT_FLOOD_ENABLE 2 // M8 +#define COOLANT_FLOOD_ENABLE PL_COND_FLAG_COOLANT_FLOOD // M8 (NOTE: Uses planner condition bit flag) +#define COOLANT_MIST_ENABLE PL_COND_FLAG_COOLANT_MIST // M7 (NOTE: Uses planner condition bit flag) // Modal Group G8: Tool length offset #define TOOL_LENGTH_OFFSET_CANCEL 0 // G49 (Default: Must be zero) @@ -155,7 +155,7 @@ typedef struct { uint8_t program_flow; // {M0,M1,M2,M30} uint8_t coolant; // {M7,M8,M9} uint8_t spindle; // {M3,M4,M5} -} gc_modal_t; +} gc_modal_t; typedef struct { float f; // Feed @@ -173,7 +173,7 @@ typedef struct { typedef struct { gc_modal_t modal; - + float spindle_speed; // RPM float feed_rate; // Millimeters/min uint8_t tool; // Tracks tool number. NOT USED. @@ -181,24 +181,21 @@ typedef struct { float position[N_AXIS]; // Where the interpreter considers the tool to be at this point in the code - float coord_system[N_AXIS]; // Current work coordinate system (G54+). Stores offset from absolute machine - // position in mm. Loaded from EEPROM when called. - 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. - float tool_length_offset; // Tracks tool length offset value when enabled. + float coord_system[N_AXIS]; // Current work coordinate system (G54+). Stores offset from absolute machine + // position in mm. Loaded from EEPROM when called. + 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. + float tool_length_offset; // Tracks tool length offset value when enabled. } parser_state_t; extern parser_state_t gc_state; -typedef struct { -// uint16_t command_words; // NOTE: If this bitflag variable fills, G and M words can be separated. -// uint16_t value_words; +typedef struct { uint8_t non_modal_command; gc_modal_t modal; gc_values_t values; - } parser_block_t; -extern parser_block_t gc_block; + // Initialize the parser void gc_init(); @@ -207,6 +204,6 @@ void gc_init(); uint8_t gc_execute_line(char *line); // Set g-code parser position. Input in steps. -void gc_sync_position(); +void gc_sync_position(); #endif diff --git a/grbl/grbl.h b/grbl/grbl.h index 8fae0b8..4dbdc7e 100644 --- a/grbl/grbl.h +++ b/grbl/grbl.h @@ -2,7 +2,7 @@ grbl.h - main Grbl include file Part of Grbl - Copyright (c) 2015 Sungeun K. Jeon + Copyright (c) 2015-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,8 +22,8 @@ #define grbl_h // Grbl versioning system -#define GRBL_VERSION "1.0c" -#define GRBL_VERSION_BUILD "20160330" +#define GRBL_VERSION "1.0e" +#define GRBL_VERSION_BUILD "20160921" // Define standard libraries used by Grbl. #include @@ -32,7 +32,7 @@ #include #include #include -#include +#include #include #include #include @@ -45,6 +45,7 @@ #include "system.h" #include "defaults.h" #include "cpu_map.h" +#include "planner.h" #include "coolant_control.h" #include "eeprom.h" #include "gcode.h" @@ -58,5 +59,6 @@ #include "serial.h" #include "spindle_control.h" #include "stepper.h" +#include "jog.h" #endif diff --git a/grbl/jog.c b/grbl/jog.c new file mode 100644 index 0000000..37010d9 --- /dev/null +++ b/grbl/jog.c @@ -0,0 +1,54 @@ +/* + jog.h - Jogging methods + Part of Grbl + + Copyright (c) 2016 Sungeun K. Jeon for Gnea Research LLC + + 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 . +*/ + +#include "grbl.h" + + +// Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. +uint8_t jog_execute(parser_block_t *gc_block) +{ + // Initialize planner data struct for motion blocks. + // NOTE: Spindle and coolant are allowed to fully function with overrides during a jog. + plan_line_data_t plan_data; + plan_line_data_t *pl_data = &plan_data; + memset(pl_data,0,sizeof(plan_line_data_t)); // Zero pl_data struct + pl_data->feed_rate = gc_block->values.f; + pl_data->spindle_speed = gc_block->values.s; // Continue current spindle and coolant condition. + plan_data.condition = (PL_COND_FLAG_NO_FEED_OVERRIDE | gc_block->modal.spindle | gc_block->modal.coolant); + #ifdef USE_LINE_NUMBERS + pl_data->line_number = JOG_LINE_NUMBER; + #endif + + if (bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)) { + if (system_check_travel_limits(gc_block->values.xyz)) { return(STATUS_TRAVEL_EXCEEDED); } + } + + // Valid jog command. Plan, set state, and execute. + mc_line(gc_block->values.xyz,pl_data); + if (sys.state == STATE_IDLE) { + if (plan_get_current_block() != NULL) { // Check if there is a block to execute. + sys.state = STATE_JOG; + st_prep_buffer(); + st_wake_up(); // NOTE: Manual start. No state machine required. + } + } + + return(STATUS_OK); +} diff --git a/grbl/jog.h b/grbl/jog.h new file mode 100644 index 0000000..d4df1e1 --- /dev/null +++ b/grbl/jog.h @@ -0,0 +1,32 @@ +/* + jog.h - Jogging methods + Part of Grbl + + Copyright (c) 2016 Sungeun K. Jeon for Gnea Research LLC + + 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 . +*/ + +#ifndef jog_h +#define jog_h + +#include "gcode.h" + +// System motion line numbers must be zero. +#define JOG_LINE_NUMBER 0 + +// Sets up valid jog motion received from g-code parser, checks for soft-limits, and executes the jog. +uint8_t jog_execute(parser_block_t *gc_block); + +#endif diff --git a/grbl/limits.c b/grbl/limits.c index b030e2b..f71f93f 100644 --- a/grbl/limits.c +++ b/grbl/limits.c @@ -2,9 +2,9 @@ limits.c - code pertaining to limit-switches and performing the homing cycle Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC 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 @@ -18,7 +18,7 @@ You should have received a copy of the GNU General Public License along with Grbl. If not, see . */ - + #include "grbl.h" @@ -30,7 +30,7 @@ #define HOMING_AXIS_LOCATE_SCALAR 5.0 // Must be > 1 to ensure limit switch is cleared. #endif -void limits_init() +void limits_init() { LIMIT_DDR &= ~(LIMIT_MASK); // Set as input pins @@ -44,9 +44,9 @@ void limits_init() LIMIT_PCMSK |= LIMIT_MASK; // Enable specific pins of the Pin Change Interrupt PCICR |= (1 << LIMIT_INT); // Enable Pin Change Interrupt } else { - limits_disable(); + limits_disable(); } - + #ifdef ENABLE_SOFTWARE_DEBOUNCE MCUSR &= ~(1<condition = (PL_COND_FLAG_SYSTEM_MOTION|PL_COND_FLAG_NO_FEED_OVERRIDE); + #ifdef USE_LINE_NUMBERS + pl_data->line_number = HOMING_CYCLE_LINE_NUMBER; + #endif + + // Initialize variables used for homing computations. uint8_t n_cycle = (2*N_HOMING_LOCATE_CYCLE+1); uint8_t step_pin[N_AXIS]; float target[N_AXIS]; float max_travel = 0.0; uint8_t idx; - for (idx=0; idxfeed_rate = homing_rate; // Set current homing rate. + plan_buffer_line(target, pl_data); // Bypass mc_line(). Directly plan homing motion. + + sys.step_control = STEP_CONTROL_EXECUTE_SYS_MOTION; // Set to execute homing motion and clear existing flags. st_prep_buffer(); // Prep and fill segment buffer from newly planned block. st_wake_up(); // Initiate motion do { @@ -219,7 +237,14 @@ void limits_go_home(uint8_t cycle_mask) limit_state = limits_get_state(); for (idx=0; idx 0); - - // The active cycle axes should now be homed and machine limits have been located. By + + // The active cycle axes should now be homed and machine limits have been located. By // default, Grbl defines machine space as all negative, as do most CNCs. Since limit switches // can be on either side of an axes, check and set axes machine zero appropriately. Also, // set up pull-off maneuver from axes limit switches that have been homed. This provides // some initial clearance off the switches and should also help prevent them from falsely // triggering when hard limits are enabled or when more than one axes shares a limit pin. - #ifdef COREXY - int32_t off_axis_position = 0; - #endif int32_t set_axis_position; // Set machine positions for homed limit switches. Don't update non-homed axes. for (idx=0; idx -settings.max_travel[idx]) { sys.soft_limit = true; } - } else { - if (target[idx] > 0 || target[idx] < settings.max_travel[idx]) { sys.soft_limit = true; } - } - #else - // NOTE: max_travel is stored as negative - if (target[idx] > 0 || target[idx] < settings.max_travel[idx]) { sys.soft_limit = true; } - #endif - - if (sys.soft_limit) { - // 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) { - system_set_exec_state_flag(EXEC_FEED_HOLD); - do { - protocol_execute_realtime(); - if (sys.abort) { return; } - } while ( sys.state != STATE_IDLE ); - } - - mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown. - system_set_exec_alarm_flag((EXEC_ALARM_SOFT_LIMIT|EXEC_CRITICAL_EVENT)); // Indicate soft limit critical event - protocol_execute_realtime(); // Execute to enter critical event loop and system abort - return; + if (system_check_travel_limits(target)) { + sys.soft_limit = true; + // 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) { + system_set_exec_state_flag(EXEC_FEED_HOLD); + do { + protocol_execute_realtime(); + if (sys.abort) { return; } + } while ( sys.state != STATE_IDLE ); } + mc_reset(); // Issue system reset and ensure spindle and coolant are shutdown. + system_set_exec_alarm(EXEC_ALARM_SOFT_LIMIT); // Indicate soft limit critical event + protocol_execute_realtime(); // Execute to enter critical event loop and system abort + return; } } diff --git a/grbl/limits.h b/grbl/limits.h index 74512ce..33fe095 100644 --- a/grbl/limits.h +++ b/grbl/limits.h @@ -2,9 +2,9 @@ limits.h - code pertaining to limit-switches and performing the homing cycle Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC 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 @@ -20,7 +20,7 @@ */ #ifndef limits_h -#define limits_h +#define limits_h // Initialize the limits module @@ -38,4 +38,4 @@ void limits_go_home(uint8_t cycle_mask); // Check for soft limit violations void limits_soft_check(float *target); -#endif \ No newline at end of file +#endif diff --git a/grbl/main.c b/grbl/main.c index 75bea14..4290904 100644 --- a/grbl/main.c +++ b/grbl/main.c @@ -1,10 +1,10 @@ /* main.c - An embedded CNC Controller with rs274/ngc (g-code) support Part of Grbl - - Copyright (c) 2011-2015 Sungeun K. Jeon + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC 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 @@ -23,7 +23,7 @@ // Declare system global variable structure -system_t sys; +system_t sys; int main(void) @@ -33,7 +33,7 @@ int main(void) settings_init(); // Load Grbl settings from EEPROM stepper_init(); // Configure stepper pins and interrupt timers system_init(); // Configure pinout pins and pin-change interrupt - + memset(&sys, 0, sizeof(system_t)); // Clear all system variables sys.abort = true; // Set abort to complete initialization sei(); // Enable interrupts @@ -48,25 +48,25 @@ int main(void) #ifdef HOMING_INIT_LOCK if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { sys.state = STATE_ALARM; } #endif - + // Force Grbl into an ALARM state upon a power-cycle or hard reset. #ifdef FORCE_INITIALIZATION_ALARM sys.state = STATE_ALARM; #endif - + // Grbl initialization loop upon power-up or a system abort. For the latter, all processes // will return to this loop to be cleanly re-initialized. for(;;) { // TODO: Separate configure task that require interrupts to be disabled, especially upon // a system abort and ensuring any active interrupts are cleanly reset. - + // Reset Grbl primary systems. serial_reset_read_buffer(); // Clear serial read buffer gc_init(); // Set g-code parser to default state spindle_init(); coolant_init(); - limits_init(); + limits_init(); probe_init(); plan_reset(); // Clear block buffer and planner variables st_reset(); // Clear stepper subsystem variables. @@ -76,15 +76,27 @@ int main(void) gc_sync_position(); // Reset system variables. - sys.abort = false; + sys.abort = sys.suspend = sys.soft_limit = false; + sys.step_control = STEP_CONTROL_NORMAL_OP; + sys.f_override = DEFAULT_FEED_OVERRIDE; + sys.r_override = DEFAULT_RAPID_OVERRIDE; + sys.spindle_speed_ovr = DEFAULT_SPINDLE_SPEED_OVERRIDE; + sys.toggle_ovr_mask = 0; + sys.report_wco_counter = REPORT_WCO_REFRESH_BUSY_COUNT; // Set to include in first report. + sys.report_ovr_counter = REPORT_OVR_REFRESH_BUSY_COUNT; // Set to include in first report. + + sys_probe_state = 0; sys_rt_exec_state = 0; sys_rt_exec_alarm = 0; - sys.suspend = false; - sys.soft_limit = false; - + sys_rt_exec_motion_override = 0; + sys_rt_exec_accessory_override = 0; + + // Print welcome message. Indicates an initialization has occured at power-up or with a reset. + report_init_message(); + // Start Grbl main loop. Processes program inputs and executes them. protocol_main_loop(); - + } return 0; /* Never reached */ } diff --git a/grbl/motion_control.c b/grbl/motion_control.c index b0c6f6e..f7c381e 100644 --- a/grbl/motion_control.c +++ b/grbl/motion_control.c @@ -2,10 +2,9 @@ motion_control.c - high level interface for issuing motion commands Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud - Copyright (c) 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 @@ -26,38 +25,37 @@ // Execute linear motion in absolute millimeter coordinates. Feed rate given in millimeters/second // unless invert_feed_rate is true. Then the feed_rate means that the motion should be completed in // (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 // mc_line and plan_buffer_line is done primarily to place non-planner-type functions from being // in the planner and to let backlash compensation or canned cycle integration simple and direct. -#ifdef USE_LINE_NUMBERS - void mc_line(float *target, float feed_rate, uint8_t invert_feed_rate, int32_t line_number) -#else - void mc_line(float *target, float feed_rate, uint8_t invert_feed_rate) -#endif +void mc_line(float *target, plan_line_data_t *pl_data) { // If enabled, check for soft limit violations. Placed here all line motions are picked up // from everywhere in Grbl. - if (bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)) { limits_soft_check(target); } - + if (bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)) { + // NOTE: Block jog state. Jogging is a special case and soft limits are handled independently. + if (sys.state != STATE_JOG) { limits_soft_check(target); } + } + // If in check gcode mode, prevent motion by blocking planner. Soft limits still work. if (sys.state == STATE_CHECK_MODE) { return; } - + // NOTE: Backlash compensation may be installed here. It will need direction info to track when // to insert a backlash line motion(s) before the intended line motion and will require its own - // plan_check_full_buffer() and check for system abort loop. Also for position reporting + // plan_check_full_buffer() and check for system abort loop. Also for position reporting // backlash steps will need to be also tracked, which will need to be kept at a system level. // There are likely some other things that will need to be tracked as well. However, we feel // that backlash compensation should NOT be handled by Grbl itself, because there are a myriad // of ways to implement it and can be effective or ineffective for different CNC machines. This // would be better handled by the interface as a post-processor task, where the original g-code - // is translated and inserts backlash motions that best suits the machine. + // is translated and inserts backlash motions that best suits the machine. // NOTE: Perhaps as a middle-ground, all that needs to be sent is a flag or special command that // indicates to Grbl what is a backlash compensation motion, so that Grbl executes the move but // doesn't update the machine position values. Since the position values used by the g-code // parser and planner are separate from the system machine positions, this is doable. - // 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. do { protocol_execute_realtime(); // Check for any run-time commands @@ -68,28 +66,19 @@ // Plan and queue motion into planner buffer // uint8_t plan_status; // Not used in normal operation. - #ifdef USE_LINE_NUMBERS - plan_buffer_line(target, feed_rate, invert_feed_rate, false, line_number); - #else - plan_buffer_line(target, feed_rate, invert_feed_rate, false); - #endif + plan_buffer_line(target, pl_data); } -// Execute an arc in offset mode format. position == current xyz, target == target xyz, +// Execute an arc in offset mode format. position == current xyz, target == target xyz, // offset == offset from current xyz, axis_X defines circle plane in tool space, axis_linear is // the direction of helical travel, radius == circle radius, isclockwise boolean. Used // for vector transformation direction. // The arc is approximated by generating a huge number of tiny, linear segments. The chordal tolerance // of each segment is configured in settings.arc_tolerance, which is defined to be the maximum normal // distance from segment to the circle when the end points both lie on the circle. -#ifdef USE_LINE_NUMBERS - void mc_arc(float *position, float *target, float *offset, float radius, float feed_rate, - uint8_t invert_feed_rate, uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, uint8_t is_clockwise_arc, int32_t line_number) -#else - void mc_arc(float *position, float *target, float *offset, float radius, float feed_rate, - uint8_t invert_feed_rate, uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, uint8_t is_clockwise_arc) -#endif +void mc_arc(float *target, plan_line_data_t *pl_data, float *position, float *offset, float radius, + uint8_t axis_0, uint8_t axis_1, uint8_t axis_linear, uint8_t is_clockwise_arc) { float center_axis0 = position[axis_0] + offset[axis_0]; float center_axis1 = position[axis_1] + offset[axis_1]; @@ -97,7 +86,7 @@ float r_axis1 = -offset[axis_1]; float rt_axis0 = target[axis_0] - center_axis0; float rt_axis1 = target[axis_1] - center_axis1; - + // CCW angle between position and target from circle center. Only one atan2() trig computation required. float angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1); if (is_clockwise_arc) { // Correct atan2 output per direction @@ -112,13 +101,13 @@ // For the intended uses of Grbl, this value shouldn't exceed 2000 for the strictest of cases. uint16_t segments = floor(fabs(0.5*angular_travel*radius)/ sqrt(settings.arc_tolerance*(2*radius - settings.arc_tolerance)) ); - - if (segments) { + + if (segments) { // Multiply inverse feed_rate to compensate for the fact that this movement is approximated - // by a number of discrete segments. The inverse feed_rate should be correct for the sum of + // by a number of discrete segments. The inverse feed_rate should be correct for the sum of // all segments. - if (invert_feed_rate) { feed_rate *= segments; } - + if (pl_data->condition & PL_COND_FLAG_INVERSE_TIME) { pl_data->feed_rate *= segments; } + float theta_per_segment = angular_travel/segments; float linear_per_segment = (target[axis_linear] - position[axis_linear])/segments; @@ -126,26 +115,26 @@ and phi is the angle of rotation. Solution approach by Jens Geisler. r_T = [cos(phi) -sin(phi); sin(phi) cos(phi] * r ; - - For arc generation, the center of the circle is the axis of rotation and the radius vector is + + For arc generation, the center of the circle is the axis of rotation and the radius vector is defined from the circle center to the initial position. Each line segment is formed by successive vector rotations. Single precision values can accumulate error greater than tool precision in rare cases. So, exact arc path correction is implemented. This approach avoids the problem of too many very expensive trig operations [sin(),cos(),tan()] which can take 100-200 usec each to compute. - + Small angle approximation may be used to reduce computation overhead further. A third-order approximation - (second order sin() has too much error) holds for most, if not, all CNC applications. Note that this - approximation will begin to accumulate a numerical drift error when theta_per_segment is greater than + (second order sin() has too much error) holds for most, if not, all CNC applications. Note that this + approximation will begin to accumulate a numerical drift error when theta_per_segment is greater than ~0.25 rad(14 deg) AND the approximation is successively used without correction several dozen times. This scenario is extremely unlikely, since segment lengths and theta_per_segment are automatically generated - and scaled by the arc tolerance setting. Only a very large arc tolerance setting, unrealistic for CNC + and scaled by the arc tolerance setting. Only a very large arc tolerance setting, unrealistic for CNC applications, would cause this numerical drift error. However, it is best to set N_ARC_CORRECTION from a low of ~4 to a high of ~20 or so to avoid trig operations while keeping arc generation accurate. - - This approximation also allows mc_arc to immediately insert a line segment into the planner + + This approximation also allows mc_arc to immediately insert a line segment into the planner without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied - a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. - This is important when there are successive arc motions. + a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead. + 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 float cos_T = 2.0 - theta_per_segment*theta_per_segment; @@ -157,16 +146,16 @@ float r_axisi; uint16_t i; uint8_t count = 0; - + for (i = 1; i 0) { do { fval *= 10.0; } while (--exp > 0); - } + } } - // Assign floating point value with correct sign. + // Assign floating point value with correct sign. if (isnegative) { *float_ptr = -fval; } else { @@ -103,7 +103,7 @@ uint8_t read_float(char *line, uint8_t *char_counter, float *float_ptr) } *char_counter = ptr - line - 1; // Set char_counter to next statement - + return(true); } @@ -116,7 +116,7 @@ void delay_sec(float seconds, uint8_t mode) if (sys.abort) { return; } if (mode == DELAY_MODE_DWELL) { protocol_execute_realtime(); - } else { // DELAY_MODE_SAFETY_DOOR + } else { // DELAY_MODE_SYS_SUSPEND // Execute rt_system() only to avoid nesting suspend loops. protocol_exec_rt_system(); if (sys.suspend & SUSPEND_RESTART_RETRACT) { return; } // Bail, if safety door reopens. @@ -128,19 +128,19 @@ void delay_sec(float seconds, uint8_t mode) // Delays variable defined milliseconds. Compiler compatibility fix for _delay_ms(), // which only accepts constants in future compiler releases. -void delay_ms(uint16_t ms) +void delay_ms(uint16_t ms) { while ( ms-- ) { _delay_ms(1); } } // Delays variable defined microseconds. Compiler compatibility fix for _delay_us(), -// which only accepts constants in future compiler releases. Written to perform more +// which only accepts constants in future compiler releases. Written to perform more // efficiently with larger delays, as the counter adds parasitic time in each iteration. -void delay_us(uint32_t us) +void delay_us(uint32_t us) { while (us) { - if (us < 10) { + if (us < 10) { _delay_us(1); us--; } else if (us < 100) { @@ -159,3 +159,32 @@ void delay_us(uint32_t us) // Simple hypotenuse computation function. float hypot_f(float x, float y) { return(sqrt(x*x + y*y)); } + + +float convert_delta_vector_to_unit_vector(float *vector) +{ + uint8_t idx; + float magnitude = 0.0; + for (idx=0; idxnominal_speed - / \ - current->entry_speed -> + \ + / \ + current->entry_speed -> + \ | + <- next->entry_speed (aka exit speed) - +-------------+ - time --> - + +-------------+ + time --> + Recalculates the motion plan according to the following basic guidelines: - + 1. Go over every feasible block sequentially in reverse order and calculate the junction speeds (i.e. current->entry_speed) such that: - a. No junction speed exceeds the pre-computed maximum junction speed limit or nominal speeds of + a. No junction speed exceeds the pre-computed maximum junction speed limit or nominal speeds of neighboring blocks. b. A block entry speed cannot exceed one reverse-computed from its exit speed (next->entry_speed) with a maximum allowable deceleration over the block travel distance. c. The last (or newest appended) block is planned from a complete stop (an exit speed of zero). - 2. Go over every block in chronological (forward) order and dial down junction speed values if + 2. Go over every block in chronological (forward) order and dial down junction speed values if a. The exit speed exceeds the one forward-computed from its entry speed with the maximum allowable acceleration over the block travel distance. - + When these stages are complete, the planner will have maximized the velocity profiles throughout the all - of the planner blocks, where every block is operating at its maximum allowable acceleration limits. In + of the planner blocks, where every block is operating at its maximum allowable acceleration limits. In other words, for all of the blocks in the planner, the plan is optimal and no further speed improvements - are possible. If a new block is added to the buffer, the plan is recomputed according to the said + are possible. If a new block is added to the buffer, the plan is recomputed according to the said guidelines for a new optimal plan. - + To increase computational efficiency of these guidelines, a set of planner block pointers have been created to indicate stop-compute points for when the planner guidelines cannot logically make any further changes or improvements to the plan when in normal operation and new blocks are streamed and added to the - planner buffer. For example, if a subset of sequential blocks in the planner have been planned and are + planner buffer. For example, if a subset of sequential blocks in the planner have been planned and are bracketed by junction velocities at their maximums (or by the first planner block as well), no new block added to the planner buffer will alter the velocity profiles within them. So we no longer have to compute them. Or, if a set of sequential blocks from the first block in the planner (or a optimal stop-compute point) are all accelerating, they are all optimal and can not be altered by a new block added to the planner buffer, as this will only further increase the plan speed to chronological blocks until a maximum junction velocity is reached. However, if the operational conditions of the plan changes from infrequently - used feed holds or feedrate overrides, the stop-compute pointers will be reset and the entire plan is + used feed holds or feedrate overrides, the stop-compute pointers will be reset and the entire plan is recomputed as stated in the general guidelines. - + Planner buffer index mapping: - - block_buffer_tail: Points to the beginning of the planner buffer. First to be executed or being executed. + - block_buffer_tail: Points to the beginning of the planner buffer. First to be executed or being executed. - block_buffer_head: Points to the buffer block after the last block in the buffer. Used to indicate whether the buffer is full or empty. As described for standard ring buffers, this block is always empty. - - next_buffer_head: Points to next planner buffer block after the buffer head block. When equal to the + - next_buffer_head: Points to next planner buffer block after the buffer head block. When equal to the buffer tail, this indicates the buffer is full. - block_buffer_planned: Points to the first buffer block after the last optimally planned block for normal - streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the - planner buffer that don't change with the addition of a new block, as describe above. In addition, - this block can never be less than block_buffer_tail and will always be pushed forward and maintain + streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the + planner buffer that don't change with the addition of a new block, as describe above. In addition, + this block can never be less than block_buffer_tail and will always be pushed forward and maintain this requirement when encountered by the plan_discard_current_block() routine during a cycle. - - NOTE: Since the planner only computes on what's in the planner buffer, some motions with lots of short + + NOTE: Since the planner only computes on what's in the planner buffer, some motions with lots of short line segments, like G2/3 arcs or complex curves, 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 + enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and then decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this happens and becomes an annoyance, there are a few simple solutions: (1) Maximize the machine acceleration. The planner - will be able to compute higher velocity profiles within the same combined distance. (2) Maximize line + will be able to compute higher velocity profiles within the same combined distance. (2) Maximize line motion(s) distance per block to a desired tolerance. The more combined distance the planner has to use, the faster it can go. (3) Maximize the planner buffer size. This also will increase the combined distance for the planner to compute over. It also increases the number of computations the planner has to perform @@ -125,14 +123,14 @@ static uint8_t plan_prev_block_index(uint8_t block_index) ARM versions should have enough memory and speed for look-ahead blocks numbering up to a hundred or more. */ -static void planner_recalculate() -{ +static void planner_recalculate() +{ // Initialize block index to the last block in the planner buffer. uint8_t block_index = plan_prev_block_index(block_buffer_head); - + // Bail. Can't do anything with one only one plan-able block. if (block_index == block_buffer_planned) { return; } - + // Reverse Pass: Coarsely maximize all possible deceleration curves back-planning from the last // block in buffer. Cease planning when the last optimal planned or tail pointer is reached. // NOTE: Forward pass will later refine and correct the reverse pass to create an optimal plan. @@ -142,19 +140,19 @@ static void planner_recalculate() // Calculate maximum entry speed for last block in buffer, where the exit speed is always zero. current->entry_speed_sqr = min( current->max_entry_speed_sqr, 2*current->acceleration*current->millimeters); - + block_index = plan_prev_block_index(block_index); if (block_index == block_buffer_planned) { // Only two plannable blocks in buffer. Reverse pass complete. // Check if the first block is the tail. If so, notify stepper to update its current parameters. if (block_index == block_buffer_tail) { st_update_plan_block_parameters(); } } else { // Three or more plan-able blocks - while (block_index != block_buffer_planned) { + while (block_index != block_buffer_planned) { next = current; current = &block_buffer[block_index]; block_index = plan_prev_block_index(block_index); // Check if next block is the tail block(=planned block). If so, update current stepper parameters. - if (block_index == block_buffer_tail) { st_update_plan_block_parameters(); } + if (block_index == block_buffer_tail) { st_update_plan_block_parameters(); } // Compute maximum entry speed decelerating over the current block from its exit speed. if (current->entry_speed_sqr != current->max_entry_speed_sqr) { @@ -166,16 +164,16 @@ static void planner_recalculate() } } } - } + } // Forward Pass: Forward plan the acceleration curve from the planned pointer onward. // Also scans for optimal plan breakpoints and appropriately updates the planned pointer. next = &block_buffer[block_buffer_planned]; // Begin at buffer planned pointer - block_index = plan_next_block_index(block_buffer_planned); + block_index = plan_next_block_index(block_buffer_planned); while (block_index != block_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. @@ -187,20 +185,26 @@ static void planner_recalculate() block_buffer_planned = block_index; // Set optimal plan pointer. } } - + // Any block set at its maximum entry speed also creates an optimal plan up to this // point in the buffer. When the plan is bracketed by either the beginning of the // buffer and a maximum entry speed or two maximum entry speeds, every block in between // cannot logically be further improved. Hence, we don't have to recompute them anymore. if (next->entry_speed_sqr == next->max_entry_speed_sqr) { block_buffer_planned = block_index; } block_index = plan_next_block_index( block_index ); - } + } } -void plan_reset() +void plan_reset() { memset(&pl, 0, sizeof(planner_t)); // Clear planner struct + plan_reset_buffer(); +} + + +void plan_reset_buffer() +{ block_buffer_tail = 0; block_buffer_head = 0; // Empty = tail next_buffer_head = 1; // plan_next_block_index(block_buffer_head) @@ -208,7 +212,7 @@ void plan_reset() } -void plan_discard_current_block() +void plan_discard_current_block() { if (block_buffer_head != block_buffer_tail) { // Discard non-empty buffer. uint8_t block_index = plan_next_block_index( block_buffer_tail ); @@ -219,24 +223,26 @@ void plan_discard_current_block() } -plan_block_t *plan_get_parking_block() +// Returns address of planner buffer block used by system motions. Called by segment generator. +plan_block_t *plan_get_system_motion_block() { return(&block_buffer[block_buffer_head]); } -plan_block_t *plan_get_current_block() +// Returns address of first planner block, if available. Called by various main program functions. +plan_block_t *plan_get_current_block() { - if (block_buffer_head == block_buffer_tail) { return(NULL); } // Buffer empty + if (block_buffer_head == block_buffer_tail) { return(NULL); } // Buffer empty return(&block_buffer[block_buffer_tail]); } -float plan_get_exec_block_exit_speed() +float plan_get_exec_block_exit_speed_sqr() { uint8_t block_index = plan_next_block_index(block_buffer_tail); if (block_index == block_buffer_head) { return( 0.0 ); } - return( sqrt( block_buffer[block_index].entry_speed_sqr ) ); + return( block_buffer[block_index].entry_speed_sqr ); } @@ -248,47 +254,89 @@ uint8_t plan_check_full_buffer() } +// Computes and returns block nominal speed based on running condition and override values. +// NOTE: All system motion commands, such as homing/parking, are not subject to overrides. +float plan_compute_profile_nominal_speed(plan_block_t *block) +{ + float nominal_speed; + if (block->condition & PL_COND_FLAG_RAPID_MOTION) { + nominal_speed = block->rapid_rate; + nominal_speed *= (0.01*sys.r_override); + } else { + nominal_speed = block->programmed_rate; + if (!(block->condition & PL_COND_FLAG_NO_FEED_OVERRIDE)) { nominal_speed *= (0.01*sys.f_override); } + if (nominal_speed > block->rapid_rate) { nominal_speed = block->rapid_rate; } + } + if (nominal_speed > MINIMUM_FEED_RATE) { return(nominal_speed); } + return(MINIMUM_FEED_RATE); +} + + +// Computes and updates the max entry speed (sqr) of the block, based on the minimum of the junction's +// previous and current nominal speeds and max junction speed. +static void plan_compute_profile_parameters(plan_block_t *block, float nominal_speed, float prev_nominal_speed) +{ + // Compute the junction maximum entry based on the minimum of the junction speed and neighboring nominal speeds. + if (nominal_speed > prev_nominal_speed) { block->max_entry_speed_sqr = prev_nominal_speed*prev_nominal_speed; } + else { block->max_entry_speed_sqr = nominal_speed*nominal_speed; } + if (block->max_entry_speed_sqr > block->max_junction_speed_sqr) { block->max_entry_speed_sqr = block->max_junction_speed_sqr; } +} + + +// Re-calculates buffered motions profile parameters upon a motion-based override change. +void plan_update_velocity_profile_parameters() +{ + uint8_t block_index = block_buffer_tail; + plan_block_t *block; + float nominal_speed; + float prev_nominal_speed = SOME_LARGE_VALUE; // Set high for first block nominal speed calculation. + while (block_index != block_buffer_head) { + block = &block_buffer[block_index]; + nominal_speed = plan_compute_profile_nominal_speed(block); + plan_compute_profile_parameters(block, nominal_speed, prev_nominal_speed); + prev_nominal_speed = nominal_speed; + block_index = plan_next_block_index(block_index); + } + pl.previous_nominal_speed = prev_nominal_speed; // Update prev nominal speed for next incoming block. +} + + /* 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 + 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). - The is_parking_motion boolean tells the planner to plan a motion in the always unused block buffer + invert_feed_rate always false). + The system motion condition tells the planner to plan a motion in the always unused block buffer head. It avoids changing the planner state and preserves the buffer to ensure subsequent gcode - motions are still planned correctly, while the stepper module only points to the block buffer head - to execute the parking motion. */ -#ifdef USE_LINE_NUMBERS - uint8_t plan_buffer_line(float *target, float feed_rate, uint8_t invert_feed_rate, uint8_t is_parking_motion, int32_t line_number) -#else - uint8_t plan_buffer_line(float *target, float feed_rate, uint8_t invert_feed_rate, uint8_t is_parking_motion) -#endif + motions are still planned correctly, while the stepper module only points to the block buffer head + to execute the special system motion. */ +uint8_t plan_buffer_line(float *target, plan_line_data_t *pl_data) { - // Prepare and initialize new block + // Prepare and initialize new block. Copy relevant pl_data for block execution. plan_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 + memset(block,0,sizeof(plan_block_t)); // Zero all block values. + block->condition = pl_data->condition; + #ifdef VARIABLE_SPINDLE + block->spindle_speed = pl_data->spindle_speed; + #endif #ifdef USE_LINE_NUMBERS - block->line_number = line_number; + block->line_number = pl_data->line_number; #endif // Compute and store initial move distance data. - // TODO: After this for-loop, we don't touch the stepper algorithm data. Might be a good idea - // to try to keep these types of things completely separate from the planner for portability. int32_t target_steps[N_AXIS], position_steps[N_AXIS]; float unit_vec[N_AXIS], delta_mm; uint8_t idx; - + // Copy position data based on type of motion being planned. - if (is_parking_motion) { memcpy(position_steps, sys.position, sizeof(sys.position)); } + if (block->condition & PL_COND_FLAG_SYSTEM_MOTION) { memcpy(position_steps, sys_position, sizeof(sys_position)); } else { memcpy(position_steps, pl.position, sizeof(pl.position)); } - + #ifdef COREXY target_steps[A_MOTOR] = lround(target[A_MOTOR]*settings.steps_per_mm[A_MOTOR]); target_steps[B_MOTOR] = lround(target[B_MOTOR]*settings.steps_per_mm[B_MOTOR]); @@ -307,9 +355,9 @@ uint8_t plan_check_full_buffer() } block->step_event_count = max(block->step_event_count, block->steps[idx]); if (idx == A_MOTOR) { - delta_mm = ((target_steps[X_AXIS]-position_steps[X_AXIS]) + (target_steps[Y_AXIS]-position_steps[Y_AXIS]))/settings.steps_per_mm[idx]; + delta_mm = (target_steps[X_AXIS]-position_steps[X_AXIS] + target_steps[Y_AXIS]-position_steps[Y_AXIS])/settings.steps_per_mm[idx]; } else if (idx == B_MOTOR) { - delta_mm = ((target_steps[X_AXIS]-position_steps[X_AXIS]) - (target_steps[Y_AXIS]-position_steps[Y_AXIS]))/settings.steps_per_mm[idx]; + delta_mm = (target_steps[X_AXIS]-position_steps[X_AXIS] - target_steps[Y_AXIS]+position_steps[Y_AXIS])/settings.steps_per_mm[idx]; } else { delta_mm = (target_steps[idx] - position_steps[idx])/settings.steps_per_mm[idx]; } @@ -319,116 +367,97 @@ uint8_t plan_check_full_buffer() block->step_event_count = max(block->step_event_count, block->steps[idx]); delta_mm = (target_steps[idx] - position_steps[idx])/settings.steps_per_mm[idx]; #endif - unit_vec[idx] = delta_mm; // Store unit vector numerator. Denominator computed later. - - // Set direction bits. Bit enabled always means direction is negative. - if (delta_mm < 0 ) { block->direction_bits |= get_direction_pin_mask(idx); } - - // Incrementally compute total move distance by Euclidean norm. First add square of each term. - block->millimeters += delta_mm*delta_mm; - } - block->millimeters = sqrt(block->millimeters); // Complete millimeters calculation with sqrt() - - // Bail if this is a zero-length block. Highly unlikely to occur. - if (block->step_event_count == 0) { return(PLAN_EMPTY_BLOCK); } - - // 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; } - if (feed_rate < MINIMUM_FEED_RATE) { feed_rate = MINIMUM_FEED_RATE; } // Prevents step generation round-off condition. + unit_vec[idx] = delta_mm; // Store unit vector numerator - // Calculate the unit vector of the line move and the block maximum feed rate and acceleration scaled - // down such that no individual axes maximum values are exceeded with respect to the line direction. + // Set direction bits. Bit enabled always means direction is negative. + if (delta_mm < 0.0 ) { block->direction_bits |= get_direction_pin_mask(idx); } + } + + // Bail if this is a zero-length block. Highly unlikely to occur. + if (block->step_event_count == 0) { return(PLAN_EMPTY_BLOCK); } + + // Calculate the unit vector of the line move and the block maximum feed rate and acceleration scaled + // down such that no individual axes maximum values are exceeded with respect to the line direction. // 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.0; - for (idx=0; idxmillimeters = convert_delta_vector_to_unit_vector(unit_vec); + block->acceleration = limit_value_by_axis_maximum(settings.acceleration, unit_vec); + block->rapid_rate = limit_value_by_axis_maximum(settings.max_rate, unit_vec); - // Check and limit feed rate against max individual axis velocities and accelerations - feed_rate = min(feed_rate,settings.max_rate[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]; - } - } + // Store programmed rate. + block->programmed_rate = pl_data->feed_rate; + if (block->condition & PL_COND_FLAG_INVERSE_TIME) { block->programmed_rate *= block->millimeters; } // TODO: Need to check this method handling zero junction speeds when starting from rest. - if ((block_buffer_head == block_buffer_tail) || is_parking_motion) { + if ((block_buffer_head == block_buffer_tail) || (block->condition & PL_COND_FLAG_SYSTEM_MOTION)) { // Initialize block entry speed as zero. Assume it will be starting from rest. Planner will correct this later. - // If parking motion, the parking block always is assumed to start from rest and end at a complete stop. + // If system motion, the system motion block always is assumed to start from rest and end at a complete stop. block->entry_speed_sqr = 0.0; block->max_junction_speed_sqr = 0.0; // Starting from rest. Enforce start from zero velocity. } else { - /* - 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. + // 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. + // + // NOTE: The max junction speed is a fixed value, since machine acceleration limits cannot be + // changed dynamically during operation nor can the line move geometry. This must be kept in + // memory in the event of a feedrate override changing the nominal speeds of blocks, which can + // change the overall maximum entry speed conditions of all blocks. + + float junction_unit_vec[N_AXIS]; + float junction_cos_theta = 0.0; + for (idx=0; idx 0.999999) { - // For a 0 degree acute junction, just set minimum junction speed. + // For a 0 degree acute junction, just set minimum junction speed. block->max_junction_speed_sqr = MINIMUM_JUNCTION_SPEED*MINIMUM_JUNCTION_SPEED; } else { - junction_cos_theta = max(junction_cos_theta,-0.999999); // Check for numerical round-off to avoid divide by zero. - float sin_theta_d2 = sqrt(0.5*(1.0-junction_cos_theta)); // Trig half angle identity. Always positive. - - // TODO: Technically, the acceleration used in calculation needs to be limited by the minimum of the - // two junctions. However, this shouldn't be a significant problem except in extreme circumstances. - block->max_junction_speed_sqr = max( MINIMUM_JUNCTION_SPEED*MINIMUM_JUNCTION_SPEED, - (block->acceleration * settings.junction_deviation * sin_theta_d2)/(1.0-sin_theta_d2) ); - + if (junction_cos_theta < -0.999999) { + // Junction is a straight line or 180 degrees. Junction speed is infinite. + block->max_junction_speed_sqr = SOME_LARGE_VALUE; + } else { + convert_delta_vector_to_unit_vector(junction_unit_vec); + float junction_acceleration = limit_value_by_axis_maximum(settings.acceleration, junction_unit_vec); + float sin_theta_d2 = sqrt(0.5*(1.0-junction_cos_theta)); // Trig half angle identity. Always positive. + block->max_junction_speed_sqr = max( MINIMUM_JUNCTION_SPEED*MINIMUM_JUNCTION_SPEED, + (junction_acceleration * settings.junction_deviation * sin_theta_d2)/(1.0-sin_theta_d2) ); + } } } - - // Store block nominal speed - block->nominal_speed_sqr = feed_rate*feed_rate; // (mm/min). Always > 0 - - // Compute the junction maximum entry based on the minimum of the junction speed and neighboring nominal speeds. - block->max_entry_speed_sqr = min(block->max_junction_speed_sqr, - min(block->nominal_speed_sqr,pl.previous_nominal_speed_sqr)); - // Block parking motion from updating this data to ensure next g-code motion is computed correctly. - if (!is_parking_motion) { - // Update previous path unit_vector and nominal speed (squared) + // Block system motion from updating this data to ensure next g-code motion is computed correctly. + if (!(block->condition & PL_COND_FLAG_SYSTEM_MOTION)) { + float nominal_speed = plan_compute_profile_nominal_speed(block); + plan_compute_profile_parameters(block, nominal_speed, pl.previous_nominal_speed); + pl.previous_nominal_speed = nominal_speed; + + // Update previous path unit_vector and planner position. 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[] // New block is all set. Update buffer head and next buffer head indices. - block_buffer_head = next_buffer_head; + block_buffer_head = next_buffer_head; next_buffer_head = plan_next_block_index(block_buffer_head); - + // Finish up by recalculating the plan with the new block. planner_recalculate(); } @@ -440,19 +469,19 @@ uint8_t plan_check_full_buffer() void plan_sync_position() { // TODO: For motor configurations not in the same coordinate frame as the machine position, - // this function needs to be updated to accomodate the difference. + // this function needs to be updated to accomodate the difference. uint8_t idx; for (idx=0; idx 0) { // buf[i++] = n % base; // n /= base; // } -// +// // for (; i > 0; i--) // serial_write(buf[i - 1] < 10 ? // '0' + buf[i - 1] : @@ -60,15 +60,33 @@ void printPgmString(const char *s) // } -// Prints an uint8 variable with base and number of desired digits. -void print_unsigned_int8(uint8_t n, uint8_t base, uint8_t digits) -{ +// Prints an uint8 variable in base 10. +void print_uint8_base10(uint8_t n) +{ + uint8_t digit_a = 0; + uint8_t digit_b = 0; + if (n >= 100) { // 100-255 + digit_a = '0' + n % 10; + n /= 10; + } + if (n >= 10) { // 10-99 + digit_b = '0' + n % 10; + n /= 10; + } + serial_write('0' + n); + if (digit_b) { serial_write(digit_b); } + if (digit_a) { serial_write(digit_a); } +} + + +// Prints an uint8 variable in base 2 with desired number of desired digits. +void print_uint8_base2_ndigit(uint8_t n, uint8_t digits) { unsigned char buf[digits]; uint8_t i = 0; for (; i < digits; i++) { - buf[i] = n % base ; - n /= base; + buf[i] = n % 2 ; + n /= 2; } for (; i > 0; i--) @@ -76,38 +94,21 @@ void print_unsigned_int8(uint8_t n, uint8_t base, uint8_t digits) } -// Prints an uint8 variable in base 2. -void print_uint8_base2(uint8_t n) { - print_unsigned_int8(n,2,8); -} - - -// Prints an uint8 variable in base 10. -void print_uint8_base10(uint8_t n) -{ - uint8_t digits; - if (n < 10) { digits = 1; } - else if (n < 100) { digits = 2; } - else { digits = 3; } - print_unsigned_int8(n,10,digits); -} - - void print_uint32_base10(uint32_t n) -{ +{ if (n == 0) { serial_write('0'); return; - } + } + + unsigned char buf[10]; + uint8_t i = 0; - unsigned char buf[10]; - uint8_t i = 0; - while (n > 0) { buf[i++] = n % 10; n /= 10; } - + for (; i > 0; i--) serial_write('0' + buf[i-1]); } @@ -127,7 +128,7 @@ void printInteger(long n) // Convert float to string by immediately converting to a long integer, which contains // more digits than a float. Number of decimal places, which are tracked by a counter, // may be set by the user. The integer is then efficiently converted to a string. -// NOTE: AVR '%' and '/' integer operations are very efficient. Bitshifting speed-up +// NOTE: AVR '%' and '/' integer operations are very efficient. Bitshifting speed-up // techniques are actually just slightly slower. Found this out the hard way. void printFloat(float n, uint8_t decimal_places) { @@ -143,25 +144,25 @@ void printFloat(float n, uint8_t decimal_places) } if (decimals) { n *= 10; } n += 0.5; // Add rounding factor. Ensures carryover through entire value. - + // Generate digits backwards and store in string. - unsigned char buf[10]; + unsigned char buf[13]; uint8_t i = 0; - uint32_t a = (long)n; + uint32_t a = (long)n; buf[decimal_places] = '.'; // Place decimal point, even if decimal places are zero. while(a > 0) { if (i == decimal_places) { i++; } // Skip decimal point location buf[i++] = (a % 10) + '0'; // Get digit a /= 10; } - while (i < decimal_places) { + while (i < decimal_places) { buf[i++] = '0'; // Fill in zeros to decimal point for (n < 1) } if (i == decimal_places) { // Fill in leading zero, if needed. i++; - buf[i++] = '0'; - } - + buf[i++] = '0'; + } + // Print the generated string. for (; i > 0; i--) serial_write(buf[i-1]); @@ -173,15 +174,15 @@ void printFloat(float n, uint8_t decimal_places) // - CoordValue: Handles all position or coordinate values in inches or mm reporting. // - RateValue: Handles feed rate and current velocity in inches or mm reporting. // - SettingValue: Handles all floating point settings values (always in mm.) -void printFloat_CoordValue(float n) { - if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) { +void printFloat_CoordValue(float n) { + if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) { printFloat(n*INCH_PER_MM,N_DECIMAL_COORDVALUE_INCH); } else { printFloat(n,N_DECIMAL_COORDVALUE_MM); } } -void printFloat_RateValue(float n) { +void printFloat_RateValue(float n) { if (bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)) { printFloat(n*INCH_PER_MM,N_DECIMAL_RATEVALUE_INCH); } else { @@ -193,13 +194,13 @@ void printFloat_SettingValue(float n) { printFloat(n,N_DECIMAL_SETTINGVALUE); } void printFloat_RPMValue(float n) { printFloat(n,N_DECIMAL_RPMVALUE); } -// Debug tool to print free memory in bytes at the called point. +// Debug tool to print free memory in bytes at the called point. // NOTE: Keep commented unless using. Part of this function always gets compiled in. // void printFreeMemory() // { -// extern int __heap_start, *__brkval; +// extern int __heap_start, *__brkval; // uint16_t free; // Up to 64k values. -// free = (int) &free - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +// free = (int) &free - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); // printInteger((int32_t)free); // printString(" "); // } diff --git a/grbl/print.h b/grbl/print.h index b8cabde..84bc784 100644 --- a/grbl/print.h +++ b/grbl/print.h @@ -2,7 +2,7 @@ print.h - Functions for formatting output strings Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -31,18 +31,15 @@ void printInteger(long n); void print_uint32_base10(uint32_t n); -// Prints uint8 variable with base and number of desired digits. -void print_unsigned_int8(uint8_t n, uint8_t base, uint8_t digits); - -// Prints an uint8 variable in base 2. -void print_uint8_base2(uint8_t n); - // Prints an uint8 variable in base 10. void print_uint8_base10(uint8_t n); +// Prints an uint8 variable in base 2 with desired number of desired digits. +void print_uint8_base2_ndigit(uint8_t n, uint8_t digits); + void printFloat(float n, uint8_t decimal_places); -// Floating value printing handlers for special variables types used in Grbl. +// Floating value printing handlers for special variables types used in Grbl. // - CoordValue: Handles all position or coordinate values in inches or mm reporting. // - RateValue: Handles feed rate and current velocity in inches or mm reporting. // - SettingValue: Handles all floating point settings values (always in mm.) @@ -55,4 +52,4 @@ void printFloat_RPMValue(float n); // Debug tool to print free memory in bytes at the called point. Not used otherwise. void printFreeMemory(); -#endif \ No newline at end of file +#endif diff --git a/grbl/probe.c b/grbl/probe.c index 5a33777..60c9073 100644 --- a/grbl/probe.c +++ b/grbl/probe.c @@ -2,7 +2,7 @@ probe.c - code pertaining to probing methods Part of Grbl - Copyright (c) 2014-2015 Sungeun K. Jeon + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with Grbl. If not, see . */ - + #include "grbl.h" @@ -26,7 +26,7 @@ uint8_t probe_invert_mask; // Probe pin initialization routine. -void probe_init() +void probe_init() { PROBE_DDR &= ~(PROBE_MASK); // Configure as input pins #ifdef DISABLE_PROBE_PIN_PULL_UP @@ -34,13 +34,13 @@ void probe_init() #else PROBE_PORT |= PROBE_MASK; // Enable internal pull-up resistors. Normal high operation. #endif - probe_configure_invert_mask(false); // Initialize invert mask. Re-updated during use. + probe_configure_invert_mask(false); // Initialize invert mask. } -// Called by probe_init() and the mc_probe() routines. Sets up the probe pin invert mask to -// appropriately set the pin logic according to setting for normal-high/normal-low operation -// and the probing cycle modes for toward-workpiece/away-from-workpiece. +// Called by probe_init() and the mc_probe() routines. Sets up the probe pin invert mask to +// appropriately set the pin logic according to setting for normal-high/normal-low operation +// and the probing cycle modes for toward-workpiece/away-from-workpiece. void probe_configure_invert_mask(uint8_t is_probe_away) { probe_invert_mask = 0; // Initialize as zero. @@ -58,11 +58,9 @@ uint8_t probe_get_state() { return((PROBE_PIN & PROBE_MASK) ^ probe_invert_mask) // NOTE: This function must be extremely efficient as to not bog down the stepper ISR. void probe_state_monitor() { - if (sys_probe_state == PROBE_ACTIVE) { - if (probe_get_state()) { - sys_probe_state = PROBE_OFF; - memcpy(sys.probe_position, sys.position, sizeof(sys.position)); - bit_true(sys_rt_exec_state, EXEC_MOTION_CANCEL); - } + if (probe_get_state()) { + sys_probe_state = PROBE_OFF; + memcpy(sys_probe_position, sys_position, sizeof(sys_position)); + bit_true(sys_rt_exec_state, EXEC_MOTION_CANCEL); } } diff --git a/grbl/probe.h b/grbl/probe.h index 81bd486..03d5fd3 100644 --- a/grbl/probe.h +++ b/grbl/probe.h @@ -2,7 +2,7 @@ probe.h - code pertaining to probing methods Part of Grbl - Copyright (c) 2014-2015 Sungeun K. Jeon + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,20 +17,20 @@ You should have received a copy of the GNU General Public License along with Grbl. If not, see . */ - -#ifndef probe_h -#define probe_h -// Values that define the probing state machine. +#ifndef probe_h +#define probe_h + +// Values that define the probing state machine. #define PROBE_OFF 0 // Probing disabled or not in use. (Must be zero.) #define PROBE_ACTIVE 1 // Actively watching the input pin. // Probe pin initialization routine. void probe_init(); -// Called by probe_init() and the mc_probe() routines. Sets up the probe pin invert mask to -// appropriately set the pin logic according to setting for normal-high/normal-low operation -// and the probing cycle modes for toward-workpiece/away-from-workpiece. +// Called by probe_init() and the mc_probe() routines. Sets up the probe pin invert mask to +// appropriately set the pin logic according to setting for normal-high/normal-low operation +// and the probing cycle modes for toward-workpiece/away-from-workpiece. void probe_configure_invert_mask(uint8_t is_probe_away); // Returns probe pin state. Triggered = true. Called by gcode parser and probe state monitor. diff --git a/grbl/protocol.c b/grbl/protocol.c index a36fd62..e167769 100644 --- a/grbl/protocol.c +++ b/grbl/protocol.c @@ -1,8 +1,8 @@ /* protocol.c - controls Grbl execution protocol and procedures Part of Grbl - - Copyright (c) 2011-2015 Sungeun K. Jeon + + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -32,35 +32,39 @@ static char line[LINE_BUFFER_SIZE]; // Line to be executed. Zero-terminated. static void protocol_exec_rt_suspend(); -/* +/* GRBL PRIMARY LOOP: */ void protocol_main_loop() { - // ------------------------------------------------------------ - // Complete initialization procedures upon a power-up or reset. - // ------------------------------------------------------------ - - // Print welcome message - report_init_message(); - + // Perform some machine checks to make sure everything is good to go. + #ifdef CHECK_LIMITS_AT_INIT + if (bit_istrue(settings.flags, BITFLAG_HARD_LIMIT_ENABLE)) { + if (limits_get_state()) { + sys.state = STATE_ALARM; // Ensure alarm state is active. + report_feedback_message(MESSAGE_CHECK_LIMITS); + } + } + #endif // Check for and report alarm state after a reset, error, or an initial power up. if (sys.state == STATE_ALARM) { - report_feedback_message(MESSAGE_ALARM_LOCK); + report_feedback_message(MESSAGE_ALARM_LOCK); } else { - // All systems go! But first check for safety door. + // Check if the safety door is open. sys.state = STATE_IDLE; if (system_check_safety_door_ajar()) { bit_true(sys_rt_exec_state, EXEC_SAFETY_DOOR); protocol_execute_realtime(); // Enter safety door mode. Should return as IDLE state. - } + } + // All systems go! system_execute_startup(line); // Execute startup script. } - - // --------------------------------------------------------------------------------- - // Primary loop! Upon a system abort, this exits back to main() to reset the system. - // --------------------------------------------------------------------------------- - + + // --------------------------------------------------------------------------------- + // Primary loop! Upon a system abort, this exits back to main() to reset the system. + // This is also where Grbl idles while waiting for something to do. + // --------------------------------------------------------------------------------- + uint8_t line_flags = 0; uint8_t char_counter = 0; uint8_t c; @@ -68,19 +72,11 @@ void protocol_main_loop() // Process one line of incoming serial data, as the data becomes available. Performs an // initial filtering by removing spaces and comments and capitalizing all letters. - - // NOTE: While comment, spaces, and block delete(if supported) handling should technically - // be done in the g-code parser, doing it here helps compress the incoming data into Grbl's - // line buffer, which is limited in size. The g-code standard actually states a line can't - // exceed 256 characters, but the Arduino Uno does not have the memory space for this. - // With a better processor, it would be very easy to pull this initial parsing out as a - // seperate task to be shared by the g-code parser and Grbl's system commands. - while((c = serial_read()) != SERIAL_NO_DATA) { if ((c == '\n') || (c == '\r')) { // End of line reached protocol_execute_realtime(); // Runtime command check point. - if (sys.abort) { return; } // Bail to calling function upon system abort + if (sys.abort) { return; } // Bail to calling function upon system abort line[char_counter] = 0; // Set string termination character. #ifdef REPORT_ECHO_LINE_RECEIVED @@ -90,27 +86,27 @@ void protocol_main_loop() // Direct and execute one line of formatted input, and report status of execution. if (line_flags & LINE_FLAG_OVERFLOW) { // Report line overflow error. - report_status_message(STATUS_OVERFLOW); + report_status_message(STATUS_OVERFLOW); } else if (line[0] == 0) { // Empty or comment line. For syncing purposes. report_status_message(STATUS_OK); } else if (line[0] == '$') { // Grbl '$' system command report_status_message(system_execute_line(line)); - } else if (sys.state == STATE_ALARM) { - // Everything else is gcode. Block if in alarm mode. - report_status_message(STATUS_ALARM_LOCK); + } else if (sys.state & (STATE_ALARM | STATE_JOG)) { + // Everything else is gcode. Block if in alarm or jog mode. + report_status_message(STATUS_SYSTEM_GC_LOCK); } else { // Parse and execute g-code block. - report_status_message(gc_execute_line(line)); + report_status_message(gc_execute_line(line)); } - + // Reset tracking data for next line. line_flags = 0; char_counter = 0; - + } else { - + if (line_flags) { // Throw away all (except EOL) comment characters and overflow characters. if (c == ')') { @@ -118,9 +114,9 @@ void protocol_main_loop() if (line_flags & LINE_FLAG_COMMENT_PARENTHESES) { line_flags &= ~(LINE_FLAG_COMMENT_PARENTHESES); } } } else { - if (c <= ' ') { - // Throw away whitepace and control characters - } else if (c == '/') { + if (c <= ' ') { + // Throw away whitepace and control characters + } else if (c == '/') { // Block delete NOT SUPPORTED. Ignore character. // NOTE: If supported, would simply need to check the system if block delete is enabled. } else if (c == '(') { @@ -132,11 +128,11 @@ void protocol_main_loop() } else if (c == ';') { // NOTE: ';' comment to EOL is a LinuxCNC definition. Not NIST. line_flags |= LINE_FLAG_COMMENT_SEMICOLON; - // TODO: Install '%' feature + // TODO: Install '%' feature // } else if (c == '%') { // Program start-end percent sign NOT SUPPORTED. // NOTE: This maybe installed to tell Grbl when a program is running vs manual input, - // where, during a program, the system auto-cycle start will continue to execute + // where, during a program, the system auto-cycle start will continue to execute // everything until the next '%' sign. This will help fix resuming issues with certain // functions that empty the planner buffer to execute its task on-time. } else if (char_counter >= (LINE_BUFFER_SIZE-1)) { @@ -148,20 +144,20 @@ void protocol_main_loop() line[char_counter++] = c; } } - + } } - + // If there are no more characters in the serial read buffer to be processed and executed, - // this indicates that g-code streaming has either filled the planner buffer or has + // this indicates that g-code streaming has either filled the planner buffer or has // completed. In either case, auto-cycle start, if enabled, any queued moves. protocol_auto_cycle_start(); protocol_execute_realtime(); // Runtime command check point. if (sys.abort) { return; } // Bail to main() program loop to reset system. - + } - + return; /* Never reached */ } @@ -179,28 +175,28 @@ void protocol_buffer_synchronize() } -// Auto-cycle start has two purposes: 1. Resumes a plan_synchronize() call from a function that -// requires the planner buffer to empty (spindle enable, dwell, etc.) 2. As a user setting that -// automatically begins the cycle when a user enters a valid motion command manually. This is -// intended as a beginners feature to help new users to understand g-code. It can be disabled -// as a beginner tool, but (1.) still operates. If disabled, the operation of cycle start is -// manually issuing a cycle start command whenever the user is ready and there is a valid motion -// command in the planner queue. -// NOTE: This function is called from the main loop, buffer sync, and mc_line() only and executes -// when one of these conditions exist respectively: There are no more blocks sent (i.e. streaming -// is finished, single commands), a command that needs to wait for the motions in the buffer to +// Auto-cycle start triggers when there is a motion ready to execute and if the main program is not +// actively parsing commands. +// NOTE: This function is called from the main loop, buffer sync, and mc_line() only and executes +// when one of these conditions exist respectively: There are no more blocks sent (i.e. streaming +// is finished, single commands), a command that needs to wait for the motions in the buffer to // execute calls a buffer sync, or the planner buffer is full and ready to go. -void protocol_auto_cycle_start() { system_set_exec_state_flag(EXEC_CYCLE_START); } +void protocol_auto_cycle_start() +{ + if (plan_get_current_block() != NULL) { // Check if there are any blocks in the buffer. + system_set_exec_state_flag(EXEC_CYCLE_START); // If so, execute them! + } +} // This function is the general interface to Grbl's real-time command execution system. It is called -// from various check points in the main program, primarily where there may be a while loop waiting -// for a buffer to clear space or any point where the execution time from the last check point may -// be more than a fraction of a second. This is a way to execute realtime commands asynchronously +// from various check points in the main program, primarily where there may be a while loop waiting +// for a buffer to clear space or any point where the execution time from the last check point may +// be more than a fraction of a second. This is a way to execute realtime commands asynchronously // (aka multitasking) with grbl's g-code parsing and planning functions. This function also serves // as an interface for the interrupts to set the system realtime flags, where only the main program // handles them, removing the need to define more computationally-expensive volatile variables. This -// also provides a controlled way to execute certain tasks without having two or more instances of +// also provides a controlled way to execute certain tasks without having two or more instances of // the same task, such as the planner recalculating the buffer upon a feedhold or overrides. // NOTE: The sys_rt_exec_state variable flags are set by any process, step or serial interrupts, pinouts, // limit switches, or the main program. @@ -211,8 +207,8 @@ void protocol_execute_realtime() } -// Executes run-time commands, when required. This function primarily operates as Grbl's state -// machine and controls the various real-time features Grbl has to offer. +// Executes run-time commands, when required. This function primarily operates as Grbl's state +// machine and controls the various real-time features Grbl has to offer. // NOTE: Do not alter this unless you know exactly what you are doing! void protocol_exec_rt_system() { @@ -223,82 +219,65 @@ void protocol_exec_rt_system() // the source of the error to the user. If critical, Grbl disables by entering an infinite // loop until system reset/abort. sys.state = STATE_ALARM; // Set system alarm state - if (rt_exec & EXEC_ALARM_HARD_LIMIT) { - report_alarm_message(ALARM_HARD_LIMIT_ERROR); - } else if (rt_exec & EXEC_ALARM_SOFT_LIMIT) { - report_alarm_message(ALARM_SOFT_LIMIT_ERROR); - } else if (rt_exec & EXEC_ALARM_ABORT_CYCLE) { - report_alarm_message(ALARM_ABORT_CYCLE); - } else if (rt_exec & EXEC_ALARM_PROBE_FAIL) { - report_alarm_message(ALARM_PROBE_FAIL); - } else if (rt_exec & EXEC_ALARM_HOMING_FAIL) { - report_alarm_message(ALARM_HOMING_FAIL); - } + report_alarm_message(rt_exec); // Halt everything upon a critical event flag. Currently hard and soft limits flag this. - if (rt_exec & EXEC_CRITICAL_EVENT) { + if ((rt_exec == EXEC_ALARM_HARD_LIMIT) || (rt_exec == EXEC_ALARM_HARD_LIMIT)) { report_feedback_message(MESSAGE_CRITICAL_EVENT); system_clear_exec_state_flag(EXEC_RESET); // Disable any existing reset - do { - // Block everything, except reset and status reports, until user issues reset or power - // cycles. Hard limits typically occur while unattended or not paying attention. Gives + do { + // Block everything, except reset and status reports, until user issues reset or power + // cycles. Hard limits typically occur while unattended or not paying attention. Gives // the user and a GUI time to do what is needed before resetting, like killing the - // incoming stream. The same could be said about soft limits. While the position is not - // lost, streaming could cause a serious crash if it continues afterwards. - -// TODO: Allow status reports during a critical alarm. Still need to think about implications of this. - // if (sys_rt_exec_state & EXEC_STATUS_REPORT) { - // report_realtime_status(); - // system_clear_exec_state_flag(EXEC_STATUS_REPORT); - // } - + // incoming stream. The same could be said about soft limits. While the position is not + // lost, continued streaming could cause a serious crash if by chance it gets executed. } while (bit_isfalse(sys_rt_exec_state,EXEC_RESET)); } system_clear_exec_alarm_flag(0xFF); // Clear all alarm flags } - + rt_exec = sys_rt_exec_state; // Copy volatile sys_rt_exec_state. if (rt_exec) { - - // Execute system abort. + + // Execute system abort. if (rt_exec & EXEC_RESET) { sys.abort = true; // Only place this is set true. return; // Nothing else to do but exit. } // Execute and serial print status - if (rt_exec & EXEC_STATUS_REPORT) { + if (rt_exec & EXEC_STATUS_REPORT) { report_realtime_status(); system_clear_exec_state_flag(EXEC_STATUS_REPORT); } - - // NOTE: The math involved to calculate the hold should be low enough for most, if not all, - // operational scenarios. Once hold is initiated, the system enters a suspend state to block - // all main program processes until either reset or resumed. - if (rt_exec & (EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR)) { - - // TODO: CHECK MODE? How to handle this? Likely nothing, since it only works when IDLE and then resets Grbl. - - // State check for allowable states for hold methods. - if ((sys.state == STATE_IDLE) || (sys.state & (STATE_CYCLE | STATE_HOMING | STATE_MOTION_CANCEL | STATE_HOLD | STATE_SAFETY_DOOR))) { - // If in CYCLE state, all hold states immediately initiate a motion HOLD. - if (sys.state == STATE_CYCLE) { - st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. - sys.step_control = STEP_CONTROL_EXECUTE_HOLD; // Initiate suspend state with active flag. + // NOTE: Once hold is initiated, the system immediately enters a suspend state to block all + // main program processes until either reset or resumed. This ensures a hold completes safely. + if (rt_exec & (EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR)) { + + // State check for allowable states for hold methods. + if ((sys.state == STATE_IDLE) || + (sys.state & (STATE_CYCLE | STATE_HOMING | STATE_HOLD | STATE_SAFETY_DOOR | STATE_JOG))) { + + // If in CYCLE or JOG states, immediately initiate a motion HOLD. + if (sys.state & (STATE_CYCLE | STATE_JOG)) { + if (!(sys.suspend & (SUSPEND_MOTION_CANCEL | SUSPEND_JOG_CANCEL))) { // Block, if already holding. + st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. + sys.step_control = STEP_CONTROL_EXECUTE_HOLD; // Initiate suspend state with active flag. + if (sys.state == STATE_JOG) { sys.suspend |= SUSPEND_JOG_CANCEL; } // Jog cancelled upon any hold event. + } } // If IDLE, Grbl is not in motion. Simply indicate suspend state and hold is complete. - if (sys.state == STATE_IDLE) { + if (sys.state == STATE_IDLE) { sys.suspend = SUSPEND_HOLD_COMPLETE; - sys.step_control = STEP_CONTROL_END_MOTION; + sys.step_control = STEP_CONTROL_END_MOTION; } - + // Execute and flag a motion cancel with deceleration and return to idle. Used primarily by probing cycle // to halt and cancel the remainder of the motion. if (rt_exec & EXEC_MOTION_CANCEL) { // MOTION_CANCEL only occurs during a CYCLE, but a HOLD and SAFETY_DOOR may been initiated beforehand // to hold the CYCLE. If so, only flag that motion cancel is complete. - if (sys.state == STATE_CYCLE) { sys.state = STATE_MOTION_CANCEL; } - // NOTE: Ensures the motion cancel is handled correctly if it is active during a HOLD or DOOR state. + // NOTE: State is still STATE_CYCLE. sys.suspend |= SUSPEND_MOTION_CANCEL; // Indicate motion cancel when resuming. } @@ -306,66 +285,67 @@ void protocol_exec_rt_system() if (rt_exec & EXEC_FEED_HOLD) { // Block SAFETY_DOOR state from prematurely changing back to HOLD, which should only // occur if the safety door switch closes. - if (sys.state != STATE_SAFETY_DOOR) { sys.state = STATE_HOLD; } + if (!(sys.state & (STATE_SAFETY_DOOR | STATE_JOG))) { sys.state = STATE_HOLD; } } // Execute a safety door stop with a feed hold and disable spindle/coolant. // NOTE: Safety door differs from feed holds by stopping everything no matter state, disables powered // devices (spindle/coolant), and blocks resuming until switch is re-engaged. if (rt_exec & EXEC_SAFETY_DOOR) { - report_feedback_message(MESSAGE_SAFETY_DOOR_AJAR); - - // Check if the safety re-opened during a restore parking motion only. Ignore if - // already retracting or parked. - if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { - if (sys.suspend & SUSPEND_INITIATE_RESTORE) { // Actively restoring - #ifdef PARKING_ENABLE - // Set hold and reset appropriate control flags to restart parking sequence. - if (sys.step_control & STEP_CONTROL_EXECUTE_PARK) { - st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. - sys.step_control = (STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_PARK); - sys.suspend &= ~(SUSPEND_HOLD_COMPLETE); - } // else NO_MOTION is active. - #endif - sys.suspend &= ~(SUSPEND_RETRACT_COMPLETE | SUSPEND_INITIATE_RESTORE | SUSPEND_RESTORE_COMPLETE); - sys.suspend |= SUSPEND_RESTART_RETRACT; + report_feedback_message(MESSAGE_SAFETY_DOOR_AJAR); + // If jogging, block safety door methods until jog cancel is complete. Just flag that it happened. + if (!(sys.suspend & SUSPEND_JOG_CANCEL)) { + // Check if the safety re-opened during a restore parking motion only. Ignore if + // already retracting or parked. + if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { + if (sys.suspend & SUSPEND_INITIATE_RESTORE) { // Actively restoring + #ifdef PARKING_ENABLE + // Set hold and reset appropriate control flags to restart parking sequence. + if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { + st_update_plan_block_parameters(); // Notify stepper module to recompute for hold deceleration. + sys.step_control = (STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION); + sys.suspend &= ~(SUSPEND_HOLD_COMPLETE); + } // else NO_MOTION is active. + #endif + sys.suspend &= ~(SUSPEND_RETRACT_COMPLETE | SUSPEND_INITIATE_RESTORE | SUSPEND_RESTORE_COMPLETE); + sys.suspend |= SUSPEND_RESTART_RETRACT; + } } + sys.state = STATE_SAFETY_DOOR; } - // NOTE: This flag doesn't change when the door closes, unlike sys.state. Ensures any parking motions - // are executed if the door switch closes and the state returns to HOLD. - sys.suspend |= SUSPEND_SAFETY_DOOR_AJAR; - sys.state = STATE_SAFETY_DOOR; + // are executed if the door switch closes and the state returns to HOLD. + sys.suspend |= SUSPEND_SAFETY_DOOR_AJAR; } - + } - - system_clear_exec_state_flag((EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR)); + + system_clear_exec_state_flag((EXEC_MOTION_CANCEL | EXEC_FEED_HOLD | EXEC_SAFETY_DOOR)); } - + // Execute a cycle start by starting the stepper interrupt to begin executing the blocks in queue. if (rt_exec & EXEC_CYCLE_START) { // Block if called at same time as the hold commands: feed hold, motion cancel, and safety door. // Ensures auto-cycle-start doesn't resume a hold without an explicit user-input. - if (!(rt_exec & (EXEC_FEED_HOLD | EXEC_MOTION_CANCEL | EXEC_SAFETY_DOOR))) { + if (!(rt_exec & (EXEC_FEED_HOLD | EXEC_MOTION_CANCEL | EXEC_SAFETY_DOOR))) { + // Resume door state when parking motion has retracted and door has been closed. + if ((sys.state == STATE_SAFETY_DOOR) && !(sys.suspend & SUSPEND_SAFETY_DOOR_AJAR)) { + if (sys.suspend & SUSPEND_RESTORE_COMPLETE) { + sys.state = STATE_IDLE; // Set to IDLE to immediately resume the cycle. + } else if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { + // Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR. + // NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and + // the retraction execution is complete, which implies the initial feed hold is not active. To + // restore normal operation, the restore procedures must be initiated by the following flag. Once, + // they are complete, it will call CYCLE_START automatically to resume and exit the suspend. + sys.suspend |= SUSPEND_INITIATE_RESTORE; + } + } // Cycle start only when IDLE or when a hold is complete and ready to resume. - // NOTE: SAFETY_DOOR is implicitly blocked. It reverts to HOLD when the door is closed. - if ((sys.state == STATE_IDLE) || ((sys.state & (STATE_HOLD | STATE_MOTION_CANCEL)) && (sys.suspend & SUSPEND_HOLD_COMPLETE))) { - if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { - if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { - if bit_isfalse(sys.suspend,SUSPEND_RESTORE_COMPLETE) { - // Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR. - // NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and - // the retraction execution is complete, which implies the initial feed hold is not active. To - // restore normal operation, the restore procedures must be initiated by the following flag. Once, - // they are complete, it will call CYCLE_START automatically to resume and exit the suspend. - sys.suspend |= SUSPEND_INITIATE_RESTORE; - } else { - bit_false(sys.suspend,SUSPEND_SAFETY_DOOR_AJAR); - } - } - } - if (!(sys.suspend & SUSPEND_SAFETY_DOOR_AJAR)) { + if ((sys.state == STATE_IDLE) || ((sys.state & STATE_HOLD) && (sys.suspend & SUSPEND_HOLD_COMPLETE))) { + if (sys.state == STATE_HOLD && (sys.toggle_ovr_mask & TOGGLE_OVR_STOP_ACTIVE_MASK)) { + sys.toggle_ovr_mask |= TOGGLE_OVR_STOP_RESTORE_CYCLE; // Set to restore in suspend routine and cycle start after. + } else { // Start cycle only if queued motions exist in planner buffer and the motion is not canceled. sys.step_control = STEP_CONTROL_NORMAL_OP; // Restore step control to normal operation if (plan_get_current_block() && bit_isfalse(sys.suspend,SUSPEND_MOTION_CANCEL)) { @@ -379,36 +359,183 @@ void protocol_exec_rt_system() } } } - } + } system_clear_exec_state_flag(EXEC_CYCLE_START); } - + +// if (rt_exec & EXEC_CYCLE_START) { +// // Block if called at same time as the hold commands: feed hold, motion cancel, and safety door. +// // Ensures auto-cycle-start doesn't resume a hold without an explicit user-input. +// if (!(rt_exec & (EXEC_FEED_HOLD | EXEC_MOTION_CANCEL | EXEC_SAFETY_DOOR))) { +// // Cycle start only when IDLE or when a hold is complete and ready to resume. +// // NOTE: SAFETY_DOOR is implicitly blocked. It reverts to HOLD when the door is closed. +// if ((sys.state == STATE_IDLE) || ((sys.state & STATE_HOLD) && (sys.suspend & SUSPEND_HOLD_COMPLETE))) { +// if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { +// if (sys.suspend & SUSPEND_RETRACT_COMPLETE) { +// if bit_isfalse(sys.suspend,SUSPEND_RESTORE_COMPLETE) { +// // Flag to re-energize powered components and restore original position, if disabled by SAFETY_DOOR. +// // NOTE: For a safety door to resume, the switch must be closed, as indicated by HOLD state, and +// // the retraction execution is complete, which implies the initial feed hold is not active. To +// // restore normal operation, the restore procedures must be initiated by the following flag. Once, +// // they are complete, it will call CYCLE_START automatically to resume and exit the suspend. +// sys.suspend |= SUSPEND_INITIATE_RESTORE; +// } else { +// bit_false(sys.suspend,SUSPEND_SAFETY_DOOR_AJAR); +// } +// } +// } +// if (!(sys.suspend & SUSPEND_SAFETY_DOOR_AJAR)) { +// // Start cycle only if queued motions exist in planner buffer and the motion is not canceled. +// sys.step_control = STEP_CONTROL_NORMAL_OP; // Restore step control to normal operation +// if (plan_get_current_block() && bit_isfalse(sys.suspend,SUSPEND_MOTION_CANCEL)) { +// sys.suspend = SUSPEND_DISABLE; // Break suspend state. +// sys.state = STATE_CYCLE; +// st_prep_buffer(); // Initialize step segment buffer before beginning cycle. +// st_wake_up(); +// } else { // Otherwise, do nothing. Set and resume IDLE state. +// sys.suspend = SUSPEND_DISABLE; // Break suspend state. +// sys.state = STATE_IDLE; +// } +// } +// } +// } +// system_clear_exec_state_flag(EXEC_CYCLE_START); +// } + if (rt_exec & EXEC_CYCLE_STOP) { - // 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 // realtime 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. + // cycle reinitializations. The stepper path should continue exactly as if nothing has happened. // NOTE: EXEC_CYCLE_STOP is set by the stepper subsystem when a cycle or feed hold completes. - if ((sys.state & (STATE_HOLD | STATE_SAFETY_DOOR)) && !(sys.soft_limit)) { + if ((sys.state & (STATE_HOLD | STATE_SAFETY_DOOR)) && !(sys.soft_limit) && !(sys.suspend & SUSPEND_JOG_CANCEL)) { // Hold complete. Set to indicate ready to resume. Remain in HOLD or DOOR states until user // has issued a resume command or reset. plan_cycle_reinitialize(); - if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { sys.suspend |= SUSPEND_HOLD_COMPLETE; } - bit_false(sys.step_control,(STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_PARK)); - } else { // Motion is complete. Includes CYCLE, HOMING, and MOTION_CANCEL states. - sys.suspend = SUSPEND_DISABLE; - sys.state = STATE_IDLE; - } + if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { sys.suspend |= SUSPEND_HOLD_COMPLETE; } + bit_false(sys.step_control,(STEP_CONTROL_EXECUTE_HOLD | STEP_CONTROL_EXECUTE_SYS_MOTION)); + } else { + // Motion complete. Includes CYCLE/JOG/HOMING states and jog cancel/motion cancel/soft limit events. + // NOTE: Motion and jog cancel both immediately return to idle after the hold completes. + if (sys.suspend & SUSPEND_JOG_CANCEL) { // For jog cancel, flush buffers and sync positions. + sys.step_control = STEP_CONTROL_NORMAL_OP; + plan_reset(); + st_reset(); + gc_sync_position(); + plan_sync_position(); + } + if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { // Only occurs when safety door opens during jog. + sys.suspend &= ~(SUSPEND_JOG_CANCEL); + sys.suspend |= SUSPEND_HOLD_COMPLETE; + sys.state = STATE_SAFETY_DOOR; + } else { + sys.suspend = SUSPEND_DISABLE; + sys.state = STATE_IDLE; + } + } system_clear_exec_state_flag(EXEC_CYCLE_STOP); } } - - // Overrides flag byte (sys.override) and execution should be installed here, since they - // are realtime and require a direct and controlled interface to the main stepper program. + + // Execute overrides. + rt_exec = sys_rt_exec_motion_override; // Copy volatile sys_rt_exec_motion_override + if (rt_exec) { + system_clear_exec_motion_overrides(); // Clear all motion override flags. + + uint8_t new_f_override = sys.f_override; + if (rt_exec & EXEC_FEED_OVR_RESET) { new_f_override = DEFAULT_FEED_OVERRIDE; } + if (rt_exec & EXEC_FEED_OVR_COARSE_PLUS) { new_f_override += FEED_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_FEED_OVR_COARSE_MINUS) { new_f_override -= FEED_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_FEED_OVR_FINE_PLUS) { new_f_override += FEED_OVERRIDE_FINE_INCREMENT; } + if (rt_exec & EXEC_FEED_OVR_FINE_MINUS) { new_f_override -= FEED_OVERRIDE_FINE_INCREMENT; } + new_f_override = min(new_f_override,MAX_FEED_RATE_OVERRIDE); + new_f_override = max(new_f_override,MIN_FEED_RATE_OVERRIDE); + + uint8_t new_r_override = sys.r_override; + if (rt_exec & EXEC_RAPID_OVR_RESET) { new_r_override = DEFAULT_RAPID_OVERRIDE; } + if (rt_exec & EXEC_RAPID_OVR_MEDIUM) { new_r_override = RAPID_OVERRIDE_MEDIUM; } + if (rt_exec & EXEC_RAPID_OVR_LOW) { new_r_override = RAPID_OVERRIDE_LOW; } + + if ((new_f_override != sys.f_override) || (new_r_override != sys.r_override)) { + sys.f_override = new_f_override; + sys.r_override = new_r_override; + sys.report_ovr_counter = REPORT_OVR_REFRESH_BUSY_COUNT; // Set to report change immediately + plan_update_velocity_profile_parameters(); + plan_cycle_reinitialize(); + } + } + + rt_exec = sys_rt_exec_accessory_override; + if (rt_exec) { + system_clear_exec_accessory_overrides(); // Clear all accessory override flags. + + // NOTE: Unlike motion overrides, spindle overrides do not require a planner reinitialization. + uint8_t last_s_override = sys.spindle_speed_ovr; + if (rt_exec & EXEC_SPINDLE_OVR_RESET) { last_s_override = DEFAULT_SPINDLE_SPEED_OVERRIDE; } + if (rt_exec & EXEC_SPINDLE_OVR_COARSE_PLUS) { last_s_override += SPINDLE_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_SPINDLE_OVR_COARSE_MINUS) { last_s_override -= SPINDLE_OVERRIDE_COARSE_INCREMENT; } + if (rt_exec & EXEC_SPINDLE_OVR_FINE_PLUS) { last_s_override += SPINDLE_OVERRIDE_FINE_INCREMENT; } + if (rt_exec & EXEC_SPINDLE_OVR_FINE_MINUS) { last_s_override -= SPINDLE_OVERRIDE_FINE_INCREMENT; } + last_s_override = min(last_s_override,MAX_SPINDLE_SPEED_OVERRIDE); + last_s_override = max(last_s_override,MIN_SPINDLE_SPEED_OVERRIDE); + + if (last_s_override != sys.spindle_speed_ovr) { + sys.spindle_speed_ovr = last_s_override; + sys.report_ovr_counter = REPORT_OVR_REFRESH_BUSY_COUNT; // Set to report change immediately + } + + uint8_t last_toggle_ovr_mask = sys.toggle_ovr_mask; + if (rt_exec & EXEC_SPINDLE_OVR_STOP) { + // Toggle allowed only while in HOLD state. + if (sys.state == STATE_HOLD) { + if (!(last_toggle_ovr_mask & TOGGLE_OVR_STOP_ACTIVE_MASK)) { last_toggle_ovr_mask |= TOGGLE_OVR_STOP_INITIATE; } + else if (last_toggle_ovr_mask & TOGGLE_OVR_STOP_ENABLED) { last_toggle_ovr_mask |= TOGGLE_OVR_STOP_RESTORE; } + } + } + + // NOTE: Since coolant state always performs a planner sync whenever it changes, g-code parser + // state can be implicitly determine current run state at the beginning of the planner. + if (rt_exec & (EXEC_COOLANT_FLOOD_OVR_TOGGLE | EXEC_COOLANT_MIST_OVR_TOGGLE)) { + if ((sys.state == STATE_IDLE) || (sys.state & (STATE_CYCLE | STATE_HOLD))) { + uint8_t coolant_state = gc_state.modal.coolant; + #ifdef ENABLE_M7 + if (rt_exec & EXEC_COOLANT_MIST_OVR_TOGGLE) { + if (coolant_state & COOLANT_MIST_ENABLE) { bit_false(coolant_state,COOLANT_MIST_ENABLE); } + else { coolant_state |= COOLANT_MIST_ENABLE; } + last_toggle_ovr_mask |= TOGGLE_OVR_MIST_COOLANT; + } + if (rt_exec & EXEC_COOLANT_FLOOD_OVR_TOGGLE) { + if (coolant_state & COOLANT_FLOOD_ENABLE) { bit_false(coolant_state,COOLANT_FLOOD_ENABLE); } + else { coolant_state |= COOLANT_FLOOD_ENABLE; } + last_toggle_ovr_mask |= TOGGLE_OVR_FLOOD_COOLANT; + } + #else + if (coolant_state & COOLANT_FLOOD_ENABLE) { bit_false(coolant_state,COOLANT_FLOOD_ENABLE); } + else { coolant_state |= COOLANT_FLOOD_ENABLE; } + last_toggle_ovr_mask |= TOGGLE_OVR_FLOOD_COOLANT; + #endif + coolant_set_state(coolant_state); + gc_state.modal.coolant = coolant_state; + } + } + + if (last_toggle_ovr_mask != sys.toggle_ovr_mask) { + sys.toggle_ovr_mask = last_toggle_ovr_mask; + sys.report_ovr_counter = REPORT_OVR_REFRESH_BUSY_COUNT; // Set to report change immediately + } + + } + + #ifdef DEBUG + if (sys_rt_exec_debug) { + report_realtime_debug(); + sys_rt_exec_debug = 0; + } + #endif // Reload step segment buffer - if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_MOTION_CANCEL | STATE_SAFETY_DOOR | STATE_HOMING)) { - st_prep_buffer(); + if (sys.state & (STATE_CYCLE | STATE_HOLD | STATE_SAFETY_DOOR | STATE_HOMING | STATE_JOG)) { + st_prep_buffer(); } } @@ -418,7 +545,7 @@ void protocol_exec_rt_system() // The system will enter this loop, create local variables for suspend tasks, and return to // whatever function that invoked the suspend, such that Grbl resumes normal operation. // This function is written in a way to promote custom parking motions. Simply use this as a -// template +// template static void protocol_exec_rt_suspend() { #ifdef PARKING_ENABLE @@ -426,131 +553,195 @@ static void protocol_exec_rt_suspend() float restore_target[N_AXIS]; float parking_target[N_AXIS]; float retract_waypoint = PARKING_PULLOUT_INCREMENT; + plan_line_data_t plan_data; + plan_line_data_t *pl_data = &plan_data; + memset(pl_data,0,sizeof(plan_line_data_t)); + pl_data->condition = (PL_COND_FLAG_SYSTEM_MOTION|PL_COND_FLAG_NO_FEED_OVERRIDE); + #ifdef USE_LINE_NUMBERS + pl_data->line_number = PARKING_MOTION_LINE_NUMBER; + #endif + #endif + + plan_block_t *block = plan_get_current_block(); + uint8_t restore_condition; + #ifdef VARIABLE_SPINDLE + float restore_spindle_speed; + if (block == NULL) { + restore_condition = (gc_state.modal.spindle | gc_state.modal.coolant); + restore_spindle_speed = gc_state.spindle_speed; + } else { + restore_condition = block->condition; + restore_spindle_speed = block->spindle_speed; + } + #else + float restore_spindle_speed = 0.0; // Without variable spindle, this value is unused. + if (block == NULL) { restore_condition = (gc_state.modal.spindle | gc_state.modal.coolant); } + else { restore_condition = block->condition; } #endif while (sys.suspend) { - + if (sys.abort) { return; } - - // Safety door manager. Handles de/re-energizing, switch state checks, and parking motions. - if ((sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) && (sys.suspend & SUSPEND_HOLD_COMPLETE)) { - - // Handles retraction motions and de-energizing. - if (bit_isfalse(sys.suspend,SUSPEND_RETRACT_COMPLETE)) { - #ifndef PARKING_ENABLE - - spindle_stop(); // De-energize - coolant_stop(); // De-energize - - #else - - // Get current position and store restore location and spindle retract waypoint. - system_convert_array_steps_to_mpos(parking_target,sys.position); - if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { - memcpy(restore_target,parking_target,sizeof(parking_target)); - retract_waypoint += restore_target[PARKING_AXIS]; - retract_waypoint = min(retract_waypoint,PARKING_TARGET); - } + // Block until initial hold is complete and the machine has stopped motion. + if (sys.suspend & SUSPEND_HOLD_COMPLETE) { + + // Safety door manager. Handles de/re-energizing, switch state checks, and parking motions. + if (sys.suspend & SUSPEND_SAFETY_DOOR_AJAR) { + + // Handles retraction motions and de-energizing. + if (bit_isfalse(sys.suspend,SUSPEND_RETRACT_COMPLETE)) { + + // Ensure any prior spindle stop override is disabled at start of safety door routine. + bit_false(sys.toggle_ovr_mask,TOGGLE_OVR_STOP_ACTIVE_MASK); + + #ifndef PARKING_ENABLE - // Execute slow pull-out parking retract motion. Parking requires homing enabled and - // the current location not exceeding the parking target location. - // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. - if ((bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) && - (parking_target[PARKING_AXIS] < PARKING_TARGET)) { - - // Retract spindle by pullout distance. Ensure retraction motion moves away from - // the workpiece and waypoint motion doesn't exceed the parking target location. - if (parking_target[PARKING_AXIS] < retract_waypoint) { - parking_target[PARKING_AXIS] = retract_waypoint; - mc_parking_motion(parking_target, PARKING_PULLOUT_RATE); - } - spindle_stop(); // De-energize - coolant_stop(); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize - // Execute fast parking retract motion to parking target location. - if (parking_target[PARKING_AXIS] < PARKING_TARGET) { - parking_target[PARKING_AXIS] = PARKING_TARGET; - mc_parking_motion(parking_target, PARKING_RATE); + #else + + // Get current position and store restore location and spindle retract waypoint. + system_convert_array_steps_to_mpos(parking_target,sys_position); + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + memcpy(restore_target,parking_target,sizeof(parking_target)); + retract_waypoint += restore_target[PARKING_AXIS]; + retract_waypoint = min(retract_waypoint,PARKING_TARGET); } - - } else { - - // Parking motion not possible. Just disable the spindle and coolant. - spindle_stop(); // De-energize - coolant_stop(); // De-energize - - } - - #endif - sys.suspend &= ~(SUSPEND_RESTART_RETRACT); - sys.suspend |= SUSPEND_RETRACT_COMPLETE; - - } else { - - // Allows resuming from parking/safety door. Actively checks if safety door is closed and ready to resume. - // NOTE: This unlocks the SAFETY_DOOR state to a HOLD state, such that CYCLE_START can activate a resume. - if (sys.state == STATE_SAFETY_DOOR) { - if (!(system_check_safety_door_ajar())) { - sys.state = STATE_HOLD; // Update to HOLD state to indicate door is closed and ready to resume. - } - } - - // Handles parking restore and safety door resume. - if (sys.suspend & SUSPEND_INITIATE_RESTORE) { - - #ifdef PARKING_ENABLE - // Execute fast restore motion to the pull-out position. Parking requires homing enabled. + // Execute slow pull-out parking retract motion. Parking requires homing enabled and + // the current location not exceeding the parking target location. // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. - if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { - // Check to ensure the motion doesn't move below pull-out position. - if (parking_target[PARKING_AXIS] <= PARKING_TARGET) { + if ((bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) && + (parking_target[PARKING_AXIS] < PARKING_TARGET)) { + + // Retract spindle by pullout distance. Ensure retraction motion moves away from + // the workpiece and waypoint motion doesn't exceed the parking target location. + if (parking_target[PARKING_AXIS] < retract_waypoint) { parking_target[PARKING_AXIS] = retract_waypoint; - mc_parking_motion(parking_target, PARKING_RATE); + pl_data->feed_rate = PARKING_PULLOUT_RATE; + mc_parking_motion(parking_target, pl_data); } + + spindle_stop(); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + + // Execute fast parking retract motion to parking target location. + if (parking_target[PARKING_AXIS] < PARKING_TARGET) { + parking_target[PARKING_AXIS] = PARKING_TARGET; + pl_data->feed_rate = PARKING_RATE; + mc_parking_motion(parking_target, pl_data); + } + + } else { + + // Parking motion not possible. Just disable the spindle and coolant. + spindle_stop(); // De-energize + coolant_set_state(COOLANT_DISABLE); // De-energize + } + #endif - - // Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle. - if (gc_state.modal.spindle != SPINDLE_DISABLE) { - // Block if safety door re-opened during prior restore actions. - if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { - spindle_set_state(gc_state.modal.spindle, gc_state.spindle_speed); - delay_sec(SAFETY_DOOR_SPINDLE_DELAY, DELAY_MODE_SAFETY_DOOR); + + sys.suspend &= ~(SUSPEND_RESTART_RETRACT); + sys.suspend |= SUSPEND_RETRACT_COMPLETE; + + } else { + + // Allows resuming from parking/safety door. Actively checks if safety door is closed and ready to resume. + // NOTE: This unlocks the SAFETY_DOOR state to a HOLD state, such that CYCLE_START can activate a resume. + if (sys.state == STATE_SAFETY_DOOR) { + if (!(system_check_safety_door_ajar())) { + sys.suspend &= ~(SUSPEND_SAFETY_DOOR_AJAR); // Reset door ajar flag to denote ready to resume. } } - if (gc_state.modal.coolant != COOLANT_DISABLE) { - // Block if safety door re-opened during prior restore actions. - if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { - coolant_set_state(gc_state.modal.coolant); - delay_sec(SAFETY_DOOR_COOLANT_DELAY, DELAY_MODE_SAFETY_DOOR); - } - } - - #ifdef PARKING_ENABLE - // Execute slow plunge motion from pull-out position to resume position. - if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { + + // Handles parking restore and safety door resume. + if (sys.suspend & SUSPEND_INITIATE_RESTORE) { + + #ifdef PARKING_ENABLE + // Execute fast restore motion to the pull-out position. Parking requires homing enabled. + // NOTE: State is will remain DOOR, until the de-energizing and retract is complete. + if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { + // Check to ensure the motion doesn't move below pull-out position. + if (parking_target[PARKING_AXIS] <= PARKING_TARGET) { + parking_target[PARKING_AXIS] = retract_waypoint; + pl_data->feed_rate = PARKING_RATE; + mc_parking_motion(parking_target, pl_data); + } + } + #endif + + // Delayed Tasks: Restart spindle and coolant, delay to power-up, then resume cycle. + if (gc_state.modal.spindle != SPINDLE_DISABLE) { // Block if safety door re-opened during prior restore actions. if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { - // Regardless if the retract parking motion was a valid/safe motion or not, the - // restore parking motion should logically be valid, either by returning to the - // original position through valid machine space or by not moving at all. - mc_parking_motion(restore_target, PARKING_PULLOUT_RATE); - } + spindle_set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), restore_spindle_speed); + delay_sec(SAFETY_DOOR_SPINDLE_DELAY, DELAY_MODE_SYS_SUSPEND); + } + } + if (gc_state.modal.coolant != COOLANT_DISABLE) { + // Block if safety door re-opened during prior restore actions. + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + coolant_set_state((restore_condition & (PL_COND_FLAG_COOLANT_FLOOD | PL_COND_FLAG_COOLANT_FLOOD))); + delay_sec(SAFETY_DOOR_COOLANT_DELAY, DELAY_MODE_SYS_SUSPEND); + } + } + + #ifdef PARKING_ENABLE + // Execute slow plunge motion from pull-out position to resume position. + if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { + // Block if safety door re-opened during prior restore actions. + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + // Regardless if the retract parking motion was a valid/safe motion or not, the + // restore parking motion should logically be valid, either by returning to the + // original position through valid machine space or by not moving at all. + pl_data->feed_rate = PARKING_PULLOUT_RATE; + mc_parking_motion(parking_target, pl_data); + } + } + #endif + + if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { + sys.suspend |= SUSPEND_RESTORE_COMPLETE; + system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. } - #endif - - if (bit_isfalse(sys.suspend,SUSPEND_RESTART_RETRACT)) { - sys.suspend |= SUSPEND_RESTORE_COMPLETE; - system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. } + } - + + + } else { + + // Feed hold manager. Controls spindle stop override states. + // NOTE: Hold ensured as completed by condition check at the beginning of suspend routine. + if (sys.toggle_ovr_mask & TOGGLE_OVR_STOP_INITIATE) { // Handles beginning of spindle stop + + bit_false(sys.toggle_ovr_mask,TOGGLE_OVR_STOP_ACTIVE_MASK); // Clear stop override state + if (gc_state.modal.spindle != SPINDLE_DISABLE) { + spindle_stop(); // De-energize + sys.toggle_ovr_mask |= TOGGLE_OVR_STOP_ENABLED; // Set stop override state to enabled, if de-energized. + } + + } else if (sys.toggle_ovr_mask & (TOGGLE_OVR_STOP_RESTORE | TOGGLE_OVR_STOP_RESTORE_CYCLE)) { // Handles restoring of spindle state + + if (gc_state.modal.spindle != SPINDLE_DISABLE) { + report_feedback_message(MESSAGE_SPINDLE_RESTORE); + spindle_set_state((restore_condition & (PL_COND_FLAG_SPINDLE_CW | PL_COND_FLAG_SPINDLE_CCW)), restore_spindle_speed); + delay_sec(SAFETY_DOOR_SPINDLE_DELAY, DELAY_MODE_SYS_SUSPEND); + } + if (sys.toggle_ovr_mask & TOGGLE_OVR_STOP_RESTORE_CYCLE) { + system_set_exec_state_flag(EXEC_CYCLE_START); // Set to resume program. + } + bit_false(sys.toggle_ovr_mask,TOGGLE_OVR_STOP_ACTIVE_MASK); // Clear stop override state + + } + } } protocol_exec_rt_system(); + } } diff --git a/grbl/protocol.h b/grbl/protocol.h index a5d00b1..7bc6e92 100644 --- a/grbl/protocol.h +++ b/grbl/protocol.h @@ -2,7 +2,7 @@ protocol.h - controls Grbl execution protocol and procedures Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -26,7 +26,7 @@ // NOTE: 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 support up to 256 // characters. In future versions, this will be increased, when we know how much extra -// memory space we can invest into here or we re-write the g-code parser not to have this +// memory space we can invest into here or we re-write the g-code parser not to have this // buffer. #ifndef LINE_BUFFER_SIZE #define LINE_BUFFER_SIZE 80 @@ -40,15 +40,6 @@ void protocol_main_loop(); void protocol_execute_realtime(); void protocol_exec_rt_system(); -// Notify the stepper subsystem to start executing the g-code program in buffer. -// void protocol_cycle_start(); - -// Reinitializes the buffer after a feed hold for a resume. -// void protocol_cycle_reinitialize(); - -// Initiates a feed hold of the running program -// void protocol_feed_hold(); - // Executes the auto cycle feature, if enabled. void protocol_auto_cycle_start(); diff --git a/grbl/report.c b/grbl/report.c index eaa79b9..e1767e2 100644 --- a/grbl/report.c +++ b/grbl/report.c @@ -2,7 +2,7 @@ report.c - reporting and messaging methods Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,11 +18,11 @@ along with Grbl. If not, see . */ -/* - This file functions as the primary feedback interface for Grbl. Any outgoing data, such +/* + This file functions as the primary feedback interface for Grbl. Any outgoing data, such as the protocol status messages, feedback messages, and status reports, are stored here. - For the most part, these functions primarily are called from protocol.c methods. If a - different style feedback is desired (i.e. JSON), then a user can change these following + For the most part, these functions primarily are called from protocol.c methods. If a + different style feedback is desired (i.e. JSON), then a user can change these following methods to accomodate their needs. */ @@ -30,86 +30,99 @@ // Handles the primary confirmation protocol response for streaming interfaces and human-feedback. -// For every incoming line, this method responds with an 'ok' for a successful command or an -// 'error:' to indicate some error event with the line or some critical system error during +// For every incoming line, this method responds with an 'ok' for a successful command or an +// 'error:' to indicate some error event with the line or some critical system error during // operation. Errors events can originate from the g-code parser, settings module, or asynchronously // from a critical error, such as a triggered hard limit. Interface should always monitor for these // responses. // NOTE: In silent mode, all error codes are greater than zero. // TODO: Install silent mode to return only numeric values, primarily for GUIs. -void report_status_message(uint8_t status_code) +void report_status_message(uint8_t status_code) { - if (status_code == 0) { // STATUS_OK - printPgmString(PSTR("ok\r\n")); - } else { - printPgmString(PSTR("error: ")); - #ifdef REPORT_GUI_MODE - print_uint8_base10(status_code); - #else - switch(status_code) { - case STATUS_EXPECTED_COMMAND_LETTER: - printPgmString(PSTR("Expected command letter")); break; - case STATUS_BAD_NUMBER_FORMAT: - printPgmString(PSTR("Bad number format")); break; - case STATUS_INVALID_STATEMENT: - printPgmString(PSTR("Invalid statement")); break; - case STATUS_NEGATIVE_VALUE: - printPgmString(PSTR("Value < 0")); break; - case STATUS_SETTING_DISABLED: - printPgmString(PSTR("Setting disabled")); break; - case STATUS_SETTING_STEP_PULSE_MIN: - printPgmString(PSTR("Value < 3 usec")); break; - case STATUS_SETTING_READ_FAIL: - printPgmString(PSTR("EEPROM read fail. Using defaults")); break; - case STATUS_IDLE_ERROR: - printPgmString(PSTR("Not idle")); break; - case STATUS_ALARM_LOCK: - printPgmString(PSTR("Alarm lock")); break; - case STATUS_SOFT_LIMIT_ERROR: - printPgmString(PSTR("Homing not enabled")); break; - case STATUS_OVERFLOW: - printPgmString(PSTR("Line overflow")); break; - #ifdef MAX_STEP_RATE_HZ - case STATUS_MAX_STEP_RATE_EXCEEDED: - printPgmString(PSTR("Step rate > 30kHz")); break; - #endif - case STATUS_CHECK_DOOR: - printPgmString(PSTR("Check Door")); break; - // Common g-code parser errors. - case STATUS_GCODE_MODAL_GROUP_VIOLATION: - printPgmString(PSTR("Modal group violation")); break; - case STATUS_GCODE_UNSUPPORTED_COMMAND: - printPgmString(PSTR("Unsupported command")); break; - case STATUS_GCODE_UNDEFINED_FEED_RATE: - printPgmString(PSTR("Undefined feed rate")); break; - default: - // Remaining g-code parser errors with error codes - printPgmString(PSTR("Invalid gcode ID:")); - print_uint8_base10(status_code); // Print error code for user reference - } - #endif - printPgmString(PSTR("\r\n")); + switch(status_code) { + case STATUS_OK: // STATUS_OK + printPgmString(PSTR("ok\r\n")); break; + default: + #ifdef REPORT_GUI_MODE + printPgmString(PSTR("error:")); + print_uint8_base10(status_code); + #else + printPgmString(PSTR("error: ")); + switch(status_code) { + case STATUS_EXPECTED_COMMAND_LETTER: + printPgmString(PSTR("Expected command letter")); break; + case STATUS_BAD_NUMBER_FORMAT: + printPgmString(PSTR("Bad number format")); break; + case STATUS_INVALID_STATEMENT: + printPgmString(PSTR("Invalid statement")); break; + case STATUS_NEGATIVE_VALUE: + printPgmString(PSTR("Value < 0")); break; + case STATUS_SETTING_DISABLED: + printPgmString(PSTR("Setting disabled")); break; + case STATUS_SETTING_STEP_PULSE_MIN: + printPgmString(PSTR("Value < 3 usec")); break; + case STATUS_SETTING_READ_FAIL: + printPgmString(PSTR("EEPROM read fail. Using defaults")); break; + case STATUS_IDLE_ERROR: + printPgmString(PSTR("Not idle")); break; + case STATUS_SYSTEM_GC_LOCK: + printPgmString(PSTR("G-code lock")); break; + case STATUS_SOFT_LIMIT_ERROR: + printPgmString(PSTR("Homing not enabled")); break; + case STATUS_OVERFLOW: + printPgmString(PSTR("Line overflow")); break; + #ifdef MAX_STEP_RATE_HZ + case STATUS_MAX_STEP_RATE_EXCEEDED: + printPgmString(PSTR("Step rate > 30kHz")); break; + #endif + case STATUS_CHECK_DOOR: + printPgmString(PSTR("Check Door")); break; + // case STATUS_LINE_LENGTH_EXCEEDED: // Supported on Grbl-Mega only. + // printPgmString(PSTR("Line length exceeded")); break; + case STATUS_TRAVEL_EXCEEDED: + printPgmString(PSTR("Travel exceeded")); break; + case STATUS_INVALID_JOG_COMMAND: + printPgmString(PSTR("Invalid jog command")); break; + // Common g-code parser errors. + case STATUS_GCODE_UNSUPPORTED_COMMAND: + printPgmString(PSTR("Unsupported command")); break; + case STATUS_GCODE_MODAL_GROUP_VIOLATION: + printPgmString(PSTR("Modal group violation")); break; + case STATUS_GCODE_UNDEFINED_FEED_RATE: + printPgmString(PSTR("Undefined feed rate")); break; + default: + // Remaining g-code parser errors with error codes + printPgmString(PSTR("Invalid gcode ID:")); + print_uint8_base10(status_code); // Print error code for user reference + } + #endif + printPgmString(PSTR("\r\n")); } } // Prints alarm messages. void report_alarm_message(int8_t alarm_code) { - printPgmString(PSTR("ALARM: ")); #ifdef REPORT_GUI_MODE + printPgmString(PSTR("ALARM:")); print_uint8_base10(alarm_code); #else + printPgmString(PSTR("ALARM: ")); switch (alarm_code) { - case ALARM_HARD_LIMIT_ERROR: - printPgmString(PSTR("Hard limit")); break; + case ALARM_HARD_LIMIT_ERROR: + printPgmString(PSTR("Hard limit")); break; case ALARM_SOFT_LIMIT_ERROR: - printPgmString(PSTR("Soft limit")); break; - case ALARM_ABORT_CYCLE: - printPgmString(PSTR("Abort during cycle")); break; - case ALARM_PROBE_FAIL: - printPgmString(PSTR("Probe fail")); break; - case ALARM_HOMING_FAIL: - printPgmString(PSTR("Homing fail")); break; + printPgmString(PSTR("Soft limit")); break; + case ALARM_ABORT_CYCLE: + printPgmString(PSTR("Abort during cycle")); break; + case ALARM_PROBE_FAIL_INITIAL: + case ALARM_PROBE_FAIL_CONTACT: + printPgmString(PSTR("Probe fail")); break; + case ALARM_HOMING_FAIL_RESET: + case ALARM_HOMING_FAIL_DOOR: + case ALARM_HOMING_FAIL_PULLOFF: + case ALARM_HOMING_FAIL_APPROACH: + printPgmString(PSTR("Homing fail")); break; } #endif printPgmString(PSTR("\r\n")); @@ -124,24 +137,27 @@ void report_alarm_message(int8_t alarm_code) // TODO: Install silence feedback messages option in settings void report_feedback_message(uint8_t message_code) { - printPgmString(PSTR("[")); switch(message_code) { case MESSAGE_CRITICAL_EVENT: - printPgmString(PSTR("Reset to continue")); break; + printPgmString(PSTR("[Reset to continue")); break; case MESSAGE_ALARM_LOCK: - printPgmString(PSTR("'$H'|'$X' to unlock")); break; + printPgmString(PSTR("['$H'|'$X' to unlock")); break; case MESSAGE_ALARM_UNLOCK: - printPgmString(PSTR("Caution: Unlocked")); break; + printPgmString(PSTR("[Caution: Unlocked")); break; case MESSAGE_ENABLED: - printPgmString(PSTR("Enabled")); break; + printPgmString(PSTR("[Enabled")); break; case MESSAGE_DISABLED: - printPgmString(PSTR("Disabled")); break; + printPgmString(PSTR("[Disabled")); break; case MESSAGE_SAFETY_DOOR_AJAR: - printPgmString(PSTR("Check Door")); break; + printPgmString(PSTR("[Check Door")); break; + case MESSAGE_CHECK_LIMITS: + printPgmString(PSTR("[Check Limits")); break; case MESSAGE_PROGRAM_END: - printPgmString(PSTR("Pgm End")); break; + printPgmString(PSTR("[Pgm End")); break; case MESSAGE_RESTORE_DEFAULTS: - printPgmString(PSTR("Restoring defaults")); break; + printPgmString(PSTR("[Restoring defaults")); break; + case MESSAGE_SPINDLE_RESTORE: + printPgmString(PSTR("[Restoring spindle")); break; } printPgmString(PSTR("]\r\n")); } @@ -155,7 +171,9 @@ void report_init_message() // Grbl help message void report_grbl_help() { - #ifndef REPORT_GUI_MODE + #ifdef REPORT_GUI_MODE + printPgmString(PSTR("[$$ $# $G $I $N $x=val $Nx=line $J=line $C $X $H ~ ! ? ctrl-x]\r\n")); + #else printPgmString(PSTR("$$ (view Grbl settings)\r\n" "$# (view # parameters)\r\n" "$G (view parser state)\r\n" @@ -163,6 +181,7 @@ void report_grbl_help() { "$N (view startup blocks)\r\n" "$x=value (save Grbl setting)\r\n" "$Nx=line (save startup block)\r\n" + "$J=line (jog)\r\n" "$C (check gcode mode)\r\n" "$X (kill alarm lock)\r\n" "$H (run homing cycle)\r\n" @@ -181,8 +200,8 @@ void report_grbl_settings() { #ifdef REPORT_GUI_MODE printPgmString(PSTR("$0=")); print_uint8_base10(settings.pulse_microseconds); printPgmString(PSTR("\r\n$1=")); print_uint8_base10(settings.stepper_idle_lock_time); - printPgmString(PSTR("\r\n$2=")); print_uint8_base10(settings.step_invert_mask); - printPgmString(PSTR("\r\n$3=")); print_uint8_base10(settings.dir_invert_mask); + printPgmString(PSTR("\r\n$2=")); print_uint8_base10(settings.step_invert_mask); + printPgmString(PSTR("\r\n$3=")); print_uint8_base10(settings.dir_invert_mask); printPgmString(PSTR("\r\n$4=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)); printPgmString(PSTR("\r\n$5=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_LIMIT_PINS)); printPgmString(PSTR("\r\n$6=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_PROBE_PIN)); @@ -200,44 +219,49 @@ void report_grbl_settings() { printPgmString(PSTR("\r\n$27=")); printFloat_SettingValue(settings.homing_pulloff); printPgmString(PSTR("\r\n$30=")); printFloat_RPMValue(settings.rpm_max); printPgmString(PSTR("\r\n$31=")); printFloat_RPMValue(settings.rpm_min); - printPgmString(PSTR("\r\n")); - #else + #ifdef VARIABLE_SPINDLE + printPgmString(PSTR("\r\n$32=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_LASER_MODE)); + #else + printPgmString(PSTR("\r\n$32=0\r\n")); + #endif + #else printPgmString(PSTR("$0=")); print_uint8_base10(settings.pulse_microseconds); printPgmString(PSTR(" (step pulse, usec)\r\n$1=")); print_uint8_base10(settings.stepper_idle_lock_time); - printPgmString(PSTR(" (step idle delay, msec)\r\n$2=")); print_uint8_base10(settings.step_invert_mask); - printPgmString(PSTR(" (step port invert mask:")); print_uint8_base2(settings.step_invert_mask); - printPgmString(PSTR(")\r\n$3=")); print_uint8_base10(settings.dir_invert_mask); - printPgmString(PSTR(" (dir port invert mask:")); print_uint8_base2(settings.dir_invert_mask); - printPgmString(PSTR(")\r\n$4=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)); + printPgmString(PSTR(" (step idle delay, msec)\r\n$2=")); print_uint8_base10(settings.step_invert_mask); + printPgmString(PSTR(" (step port invert mask)\r\n$3=")); print_uint8_base10(settings.dir_invert_mask); + printPgmString(PSTR(" (dir port invert mask)\r\n$4=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)); printPgmString(PSTR(" (step enable invert, bool)\r\n$5=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_LIMIT_PINS)); printPgmString(PSTR(" (limit pins invert, bool)\r\n$6=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_INVERT_PROBE_PIN)); printPgmString(PSTR(" (probe pin invert, bool)\r\n$10=")); print_uint8_base10(settings.status_report_mask); - printPgmString(PSTR(" (status report mask:")); print_uint8_base2(settings.status_report_mask); - printPgmString(PSTR(")\r\n$11=")); printFloat_SettingValue(settings.junction_deviation); + printPgmString(PSTR(" (status report mask)\r\n$11=")); printFloat_SettingValue(settings.junction_deviation); printPgmString(PSTR(" (junction deviation, mm)\r\n$12=")); printFloat_SettingValue(settings.arc_tolerance); printPgmString(PSTR(" (arc tolerance, mm)\r\n$13=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_REPORT_INCHES)); printPgmString(PSTR(" (report inches, bool)\r\n$20=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_SOFT_LIMIT_ENABLE)); printPgmString(PSTR(" (soft limits, bool)\r\n$21=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_HARD_LIMIT_ENABLE)); printPgmString(PSTR(" (hard limits, bool)\r\n$22=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)); printPgmString(PSTR(" (homing cycle, bool)\r\n$23=")); print_uint8_base10(settings.homing_dir_mask); - printPgmString(PSTR(" (homing dir invert mask:")); print_uint8_base2(settings.homing_dir_mask); - printPgmString(PSTR(")\r\n$24=")); printFloat_SettingValue(settings.homing_feed_rate); + printPgmString(PSTR(" (homing dir invert mask\r\n$24=")); printFloat_SettingValue(settings.homing_feed_rate); printPgmString(PSTR(" (homing feed, mm/min)\r\n$25=")); printFloat_SettingValue(settings.homing_seek_rate); printPgmString(PSTR(" (homing seek, mm/min)\r\n$26=")); print_uint8_base10(settings.homing_debounce_delay); printPgmString(PSTR(" (homing debounce, msec)\r\n$27=")); printFloat_SettingValue(settings.homing_pulloff); printPgmString(PSTR(" (homing pull-off, mm)\r\n$30=")); printFloat_RPMValue(settings.rpm_max); printPgmString(PSTR(" (rpm max)\r\n$31=")); printFloat_RPMValue(settings.rpm_min); - printPgmString(PSTR(" (rpm min)\r\n")); + #ifdef VARIABLE_SPINDLE + printPgmString(PSTR(" (rpm min)\r\n$32=")); print_uint8_base10(bit_istrue(settings.flags,BITFLAG_LASER_MODE)); + printPgmString(PSTR(" (laser mode, bool)\r\n")); + #else + printPgmString(PSTR(" (rpm min)\r\n$32=0 (laser mode, bool)\r\n")); + #endif #endif - + // Print axis settings uint8_t idx, set_idx; uint8_t val = AXIS_SETTINGS_START_VAL; for (set_idx=0; set_idxline_number; + } + printInteger(ln); + #endif + + #ifdef REPORT_REALTIME_RATE + // Report realtime rate + printPgmString(PSTR(",F:")); + printFloat_RateValue(st_get_realtime_rate()); + #endif + + #ifdef REPORT_ALL_PIN_STATES + if (bit_istrue(settings.status_report_mask, + ( BITFLAG_RT_STATUS_LIMIT_PINS| BITFLAG_RT_STATUS_PROBE_PIN | BITFLAG_RT_STATUS_CONTROL_PINS ))) { + printPgmString(PSTR(",Pin:")); + if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_LIMIT_PINS)) { + print_uint8_base2_ndigit(limits_get_state(),N_AXIS); + } + printPgmString(PSTR("|")); + if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_PROBE_PIN)) { + if (probe_get_state()) { printPgmString(PSTR("1")); } + else { printPgmString(PSTR("0")); } + } + printPgmString(PSTR("|")); + if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_CONTROL_PINS)) { + print_uint8_base2_ndigit(system_control_get_state(),N_CONTROL_PIN); + } + } + #else + if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_LIMIT_PINS)) { + printPgmString(PSTR(",Lim:")); + print_uint8_base2_ndigit(limits_get_state(),N_AXIS); + } + #endif + + if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_OVERRIDES)) { + printPgmString(PSTR(",Ov:")); + print_uint8_base10(sys.f_override); + serial_write(','); + print_uint8_base10(sys.r_override); + serial_write(','); + print_uint8_base10(sys.spindle_speed_ovr); + if (sys.toggle_ovr_mask) { + printPgmString(PSTR("|T:")); + if (sys.toggle_ovr_mask & TOGGLE_OVR_STOP_ACTIVE_MASK) { serial_write('S'); } + if (sys.toggle_ovr_mask & TOGGLE_OVR_FLOOD_COOLANT) { serial_write('F'); } + #ifdef ENABLE_M7 + if (sys.toggle_ovr_mask & TOGGLE_OVR_MIST_COOLANT) { serial_write('M'); } + #endif + bit_false(sys.toggle_ovr_mask, (TOGGLE_OVR_FLOOD_COOLANT|TOGGLE_OVR_FLOOD_COOLANT)); + } + } + + printPgmString(PSTR(">\r\n")); + #else - if (bit_istrue(settings.status_report_mask,BITFLAG_RT_STATUS_LIMIT_PINS)) { - printPgmString(PSTR(",Lim:")); - print_unsigned_int8(limits_get_state(),2,N_AXIS); + + uint8_t idx; + int32_t current_position[N_AXIS]; // Copy current state of the system position variable + memcpy(current_position,sys_position,sizeof(sys_position)); + float print_position[N_AXIS]; + system_convert_array_steps_to_mpos(print_position,current_position); + + // Report current machine state and sub-states + switch (sys.state) { + case STATE_IDLE: printPgmString(PSTR("line_number; + if (ln > 0) { + printPgmString(PSTR("|Ln:")); + printInteger(ln); + } + } + #endif + #endif + + // Report realtime rate + #ifdef REPORT_FIELD_CURRENT_RATE + printPgmString(PSTR("|F:")); + printFloat_RateValue(st_get_realtime_rate()); + #endif + + #ifdef REPORT_FIELD_PIN_STATE + uint8_t lim_pin_state = limits_get_state(); + uint8_t ctrl_pin_state = system_control_get_state(); + uint8_t prb_pin_state = probe_get_state(); + if (lim_pin_state | ctrl_pin_state | prb_pin_state) { + printPgmString(PSTR("|Pn:")); + if (prb_pin_state) { serial_write('P'); } + if (lim_pin_state) { + if (bit_istrue(lim_pin_state,bit(X_AXIS))) { serial_write('X'); } + if (bit_istrue(lim_pin_state,bit(Y_AXIS))) { serial_write('Y'); } + if (bit_istrue(lim_pin_state,bit(Z_AXIS))) { serial_write('Z'); } + } + if (ctrl_pin_state) { + #ifdef ENABLE_SAFETY_DOOR_INPUT_PIN + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_SAFETY_DOOR)) { serial_write('D'); } + #endif + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_RESET)) { serial_write('R'); } + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_FEED_HOLD)) { serial_write('H'); } + if (bit_istrue(ctrl_pin_state,CONTROL_PIN_INDEX_CYCLE_START)) { serial_write('S'); } + } + } + #endif + + #ifdef REPORT_FIELD_WORK_COORD_OFFSET + if (sys.report_wco_counter++ >= REPORT_WCO_REFRESH_BUSY_COUNT) { + if (sys.state & (STATE_HOMING | STATE_CYCLE | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)) { + sys.report_wco_counter = 1; // Reset counter for slow refresh + } else { sys.report_wco_counter = (REPORT_WCO_REFRESH_BUSY_COUNT-REPORT_WCO_REFRESH_IDLE_COUNT+1); } + if (sys.report_ovr_counter >= REPORT_OVR_REFRESH_BUSY_COUNT) { + sys.report_ovr_counter = (REPORT_OVR_REFRESH_BUSY_COUNT-1); // Set override on next report. + } + printPgmString(PSTR("|WCO:")); + float axis_offset; + uint8_t idx; + for (idx=0; idx= REPORT_OVR_REFRESH_BUSY_COUNT) { + if (sys.state & (STATE_HOMING | STATE_CYCLE | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)) { + sys.report_ovr_counter = 1; // Reset counter for slow refresh + } else { sys.report_ovr_counter = (REPORT_OVR_REFRESH_BUSY_COUNT-REPORT_OVR_REFRESH_IDLE_COUNT+1); } + printPgmString(PSTR("|Ov:")); + print_uint8_base10(sys.f_override); + serial_write(','); + print_uint8_base10(sys.r_override); + serial_write(','); + print_uint8_base10(sys.spindle_speed_ovr); + + if (sys.toggle_ovr_mask) { + printPgmString(PSTR("|T:")); + if (sys.toggle_ovr_mask & TOGGLE_OVR_STOP_ACTIVE_MASK) { serial_write('S'); } + if (sys.toggle_ovr_mask & TOGGLE_OVR_FLOOD_COOLANT) { serial_write('F'); } + #ifdef ENABLE_M7 + if (sys.toggle_ovr_mask & TOGGLE_OVR_MIST_COOLANT) { serial_write('M'); } + #endif + bit_false(sys.toggle_ovr_mask, (TOGGLE_OVR_FLOOD_COOLANT|TOGGLE_OVR_FLOOD_COOLANT)); + } + } + #endif + + printPgmString(PSTR(">\r\n")); + #endif - - printPgmString(PSTR(">\r\n")); } + + +#ifdef DEBUG + void report_realtime_debug() + { + + } +#endif diff --git a/grbl/report.h b/grbl/report.h index f4349b0..ba45914 100644 --- a/grbl/report.h +++ b/grbl/report.h @@ -2,7 +2,7 @@ report.h - reporting and messaging methods Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ #ifndef report_h #define report_h -// Define Grbl status codes. +// Define Grbl status codes. Valid values (0-255) #define STATUS_OK 0 #define STATUS_EXPECTED_COMMAND_LETTER 1 #define STATUS_BAD_NUMBER_FORMAT 2 @@ -30,11 +30,14 @@ #define STATUS_SETTING_STEP_PULSE_MIN 6 #define STATUS_SETTING_READ_FAIL 7 #define STATUS_IDLE_ERROR 8 -#define STATUS_ALARM_LOCK 9 +#define STATUS_SYSTEM_GC_LOCK 9 #define STATUS_SOFT_LIMIT_ERROR 10 #define STATUS_OVERFLOW 11 #define STATUS_MAX_STEP_RATE_EXCEEDED 12 #define STATUS_CHECK_DOOR 13 +#define STATUS_LINE_LENGTH_EXCEEDED 14 +#define STATUS_TRAVEL_EXCEEDED 15 +#define STATUS_INVALID_JOG_COMMAND 16 #define STATUS_GCODE_UNSUPPORTED_COMMAND 20 #define STATUS_GCODE_MODAL_GROUP_VIOLATION 21 @@ -55,22 +58,28 @@ #define STATUS_GCODE_UNUSED_WORDS 36 #define STATUS_GCODE_G43_DYNAMIC_AXIS_ERROR 37 -// Define Grbl alarm codes. -#define ALARM_HARD_LIMIT_ERROR 1 -#define ALARM_SOFT_LIMIT_ERROR 2 -#define ALARM_ABORT_CYCLE 3 -#define ALARM_PROBE_FAIL 4 -#define ALARM_HOMING_FAIL 5 +// Define Grbl alarm codes. Valid values (1-255). 0 is reserved. +#define ALARM_HARD_LIMIT_ERROR EXEC_ALARM_HARD_LIMIT +#define ALARM_SOFT_LIMIT_ERROR EXEC_ALARM_SOFT_LIMIT +#define ALARM_ABORT_CYCLE EXEC_ALARM_ABORT_CYCLE +#define ALARM_PROBE_FAIL_INITIAL EXEC_ALARM_PROBE_FAIL_INITIAL +#define ALARM_PROBE_FAIL_CONTACT EXEC_ALARM_PROBE_FAIL_CONTACT +#define ALARM_HOMING_FAIL_RESET EXEC_ALARM_HOMING_FAIL_RESET +#define ALARM_HOMING_FAIL_DOOR EXEC_ALARM_HOMING_FAIL_DOOR +#define ALARM_HOMING_FAIL_PULLOFF EXEC_ALARM_HOMING_FAIL_PULLOFF +#define ALARM_HOMING_FAIL_APPROACH EXEC_ALARM_HOMING_FAIL_APPROACH -// Define Grbl feedback message codes. +// Define Grbl feedback message codes. Valid values (0-255). #define MESSAGE_CRITICAL_EVENT 1 #define MESSAGE_ALARM_LOCK 2 #define MESSAGE_ALARM_UNLOCK 3 #define MESSAGE_ENABLED 4 #define MESSAGE_DISABLED 5 #define MESSAGE_SAFETY_DOOR_AJAR 6 -#define MESSAGE_PROGRAM_END 7 -#define MESSAGE_RESTORE_DEFAULTS 8 +#define MESSAGE_CHECK_LIMITS 7 +#define MESSAGE_PROGRAM_END 8 +#define MESSAGE_RESTORE_DEFAULTS 9 +#define MESSAGE_SPINDLE_RESTORE 10 // Prints system status messages. void report_status_message(uint8_t status_code); @@ -111,4 +120,8 @@ void report_startup_line(uint8_t n, char *line); // Prints build info and user info void report_build_info(char *line); +#ifdef DEBUG + void report_realtime_debug(); +#endif + #endif diff --git a/grbl/serial.c b/grbl/serial.c index d7d54dd..30be8ba 100644 --- a/grbl/serial.c +++ b/grbl/serial.c @@ -2,7 +2,7 @@ serial.c - Low level functions for sending and recieving bytes via the serial port Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -21,12 +21,14 @@ #include "grbl.h" +#define RX_RING_BUFFER (RX_BUFFER_SIZE+1) +#define TX_RING_BUFFER (TX_BUFFER_SIZE+1) -uint8_t serial_rx_buffer[RX_BUFFER_SIZE]; +uint8_t serial_rx_buffer[RX_RING_BUFFER]; uint8_t serial_rx_buffer_head = 0; volatile uint8_t serial_rx_buffer_tail = 0; -uint8_t serial_tx_buffer[TX_BUFFER_SIZE]; +uint8_t serial_tx_buffer[TX_RING_BUFFER]; uint8_t serial_tx_buffer_head = 0; volatile uint8_t serial_tx_buffer_tail = 0; @@ -34,14 +36,14 @@ volatile uint8_t serial_tx_buffer_tail = 0; #ifdef ENABLE_XONXOFF volatile uint8_t flow_ctrl = XON_SENT; // Flow control state variable #endif - + // Returns the number of bytes used in the RX serial buffer. uint8_t serial_get_rx_buffer_count() { - uint8_t rtail = serial_rx_buffer_tail; // Copy to limit multiple calls to volatile - if (serial_rx_buffer_head >= rtail) { return(serial_rx_buffer_head-rtail); } - return (RX_BUFFER_SIZE - (rtail-serial_rx_buffer_head)); + uint8_t diff = serial_rx_buffer_head-serial_rx_buffer_tail; + if (diff >= 0) { return(diff); } + return (RX_RING_BUFFER + diff); } @@ -51,7 +53,7 @@ uint8_t serial_get_tx_buffer_count() { uint8_t ttail = serial_tx_buffer_tail; // Copy to limit multiple calls to volatile if (serial_tx_buffer_head >= ttail) { return(serial_tx_buffer_head-ttail); } - return (TX_BUFFER_SIZE - (ttail-serial_tx_buffer_head)); + return (TX_RING_BUFFER - (ttail-serial_tx_buffer_head)); } @@ -67,14 +69,10 @@ void serial_init() #endif UBRR0H = UBRR0_value >> 8; UBRR0L = UBRR0_value; - - // enable rx and tx - UCSR0B |= 1<= RX_BUFFER_FULL) && flow_ctrl == XON_SENT) { - flow_ctrl = SEND_XOFF; - UCSR0B |= (1 << UDRIE0); // Force TX - } - #endif - + default : + if (data > 0x7F) { // Real-time control characters are extended ACSII only. + switch(data) { + case CMD_SAFETY_DOOR: system_set_exec_state_flag(EXEC_SAFETY_DOOR); break; // Set as true + #ifdef DEBUG + case CMD_DEBUG_REPORT: {uint8_t sreg = SREG; cli(); bit_true(sys_rt_exec_debug,EXEC_DEBUG_REPORT); SREG = sreg;} break; + #endif + case CMD_FEED_OVR_RESET: system_set_exec_motion_override_flag(EXEC_FEED_OVR_RESET); break; + case CMD_FEED_OVR_COARSE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_PLUS); break; + case CMD_FEED_OVR_COARSE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_COARSE_MINUS); break; + case CMD_FEED_OVR_FINE_PLUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_PLUS); break; + case CMD_FEED_OVR_FINE_MINUS: system_set_exec_motion_override_flag(EXEC_FEED_OVR_FINE_MINUS); break; + case CMD_RAPID_OVR_RESET: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_RESET); break; + case CMD_RAPID_OVR_MEDIUM: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_MEDIUM); break; + case CMD_RAPID_OVR_LOW: system_set_exec_motion_override_flag(EXEC_RAPID_OVR_LOW); break; + case CMD_SPINDLE_OVR_RESET: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_RESET); break; + case CMD_SPINDLE_OVR_COARSE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_PLUS); break; + case CMD_SPINDLE_OVR_COARSE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_COARSE_MINUS); break; + case CMD_SPINDLE_OVR_FINE_PLUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_PLUS); break; + case CMD_SPINDLE_OVR_FINE_MINUS: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_FINE_MINUS); break; + case CMD_SPINDLE_OVR_STOP: system_set_exec_accessory_override_flag(EXEC_SPINDLE_OVR_STOP); break; + case CMD_COOLANT_FLOOD_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_FLOOD_OVR_TOGGLE); break; + #ifdef ENABLE_M7 + case CMD_COOLANT_MIST_OVR_TOGGLE: system_set_exec_accessory_override_flag(EXEC_COOLANT_MIST_OVR_TOGGLE); break; + #endif + } + // Throw away any unfound extended-ASCII character by not passing it to the serial buffer. + } else { // Write character to buffer + next_head = serial_rx_buffer_head + 1; + if (next_head == RX_RING_BUFFER) { next_head = 0; } + + // Write data to buffer unless it is full. + if (next_head != serial_rx_buffer_tail) { + serial_rx_buffer[serial_rx_buffer_head] = data; + serial_rx_buffer_head = next_head; + + #ifdef ENABLE_XONXOFF + if ((serial_get_rx_buffer_count() >= RX_BUFFER_FULL) && flow_ctrl == XON_SENT) { + flow_ctrl = SEND_XOFF; + UCSR0B |= (1 << UDRIE0); // Force TX + } + #endif + + } } - //TODO: else alarm on overflow? } } -void serial_reset_read_buffer() +void serial_reset_read_buffer() { serial_rx_buffer_tail = serial_rx_buffer_head; diff --git a/grbl/serial.h b/grbl/serial.h index b48fd35..0f01cf8 100644 --- a/grbl/serial.h +++ b/grbl/serial.h @@ -2,7 +2,7 @@ serial.c - Low level functions for sending and recieving bytes via the serial port Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -27,7 +27,11 @@ #define RX_BUFFER_SIZE 128 #endif #ifndef TX_BUFFER_SIZE - #define TX_BUFFER_SIZE 64 + #ifdef USE_LINE_NUMBERS + #define TX_BUFFER_SIZE 100 + #else + #define TX_BUFFER_SIZE 90 + #endif #endif #define SERIAL_NO_DATA 0xff diff --git a/grbl/settings.c b/grbl/settings.c index 924ea9f..68c5e64 100644 --- a/grbl/settings.c +++ b/grbl/settings.c @@ -1,8 +1,8 @@ /* - settings.c - eeprom configuration handling + settings.c - eeprom configuration handling Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -27,95 +27,113 @@ settings_t settings; // Method to store startup lines into EEPROM void settings_store_startup_line(uint8_t n, char *line) { + #ifdef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE + // TODO: Alter the startup line parsing to prevent motions from being executed before this call. + // Implement it like the jog parsing. + protocol_buffer_synchronize(); // A startup line may contain a motion and be executing. + #endif uint32_t addr = n*(LINE_BUFFER_SIZE+1)+EEPROM_ADDR_STARTUP_BLOCK; memcpy_to_eeprom_with_checksum(addr,(char*)line, LINE_BUFFER_SIZE); } // Method to store build info into EEPROM +// NOTE: This function can only be called in IDLE state. void settings_store_build_info(char *line) { + // Build info can only be stored when state is IDLE. memcpy_to_eeprom_with_checksum(EEPROM_ADDR_BUILD_INFO,(char*)line, LINE_BUFFER_SIZE); } // Method to store coord data parameters into EEPROM void settings_write_coord_data(uint8_t coord_select, float *coord_data) -{ +{ + #ifdef FORCE_BUFFER_SYNC_DURING_EEPROM_WRITE + protocol_buffer_synchronize(); + #endif uint32_t addr = coord_select*(sizeof(float)*N_AXIS+1) + EEPROM_ADDR_PARAMETERS; memcpy_to_eeprom_with_checksum(addr,(char*)coord_data, sizeof(float)*N_AXIS); -} +} // Method to store Grbl global settings struct and version number into EEPROM -void write_global_settings() +// NOTE: This function can only be called in IDLE state. +void write_global_settings() { eeprom_put_char(0, SETTINGS_VERSION); memcpy_to_eeprom_with_checksum(EEPROM_ADDR_GLOBAL, (char*)&settings, sizeof(settings_t)); } -// Method to restore EEPROM-saved Grbl global settings back to defaults. -void settings_restore(uint8_t restore_flag) { +// Method to restore EEPROM-saved Grbl global settings back to defaults. +void settings_restore(uint8_t restore_flag) { if (restore_flag & SETTINGS_RESTORE_DEFAULTS) { - settings.pulse_microseconds = DEFAULT_STEP_PULSE_MICROSECONDS; - settings.stepper_idle_lock_time = DEFAULT_STEPPER_IDLE_LOCK_TIME; - settings.step_invert_mask = DEFAULT_STEPPING_INVERT_MASK; - settings.dir_invert_mask = DEFAULT_DIRECTION_INVERT_MASK; - settings.status_report_mask = DEFAULT_STATUS_REPORT_MASK; - settings.junction_deviation = DEFAULT_JUNCTION_DEVIATION; - settings.arc_tolerance = DEFAULT_ARC_TOLERANCE; - - settings.rpm_max = DEFAULT_SPINDLE_RPM_MAX; - settings.rpm_min = DEFAULT_SPINDLE_RPM_MIN; - - settings.homing_dir_mask = DEFAULT_HOMING_DIR_MASK; - settings.homing_feed_rate = DEFAULT_HOMING_FEED_RATE; - settings.homing_seek_rate = DEFAULT_HOMING_SEEK_RATE; - settings.homing_debounce_delay = DEFAULT_HOMING_DEBOUNCE_DELAY; - settings.homing_pulloff = DEFAULT_HOMING_PULLOFF; + settings.pulse_microseconds = DEFAULT_STEP_PULSE_MICROSECONDS; + settings.stepper_idle_lock_time = DEFAULT_STEPPER_IDLE_LOCK_TIME; + settings.step_invert_mask = DEFAULT_STEPPING_INVERT_MASK; + settings.dir_invert_mask = DEFAULT_DIRECTION_INVERT_MASK; + settings.status_report_mask = DEFAULT_STATUS_REPORT_MASK; + settings.junction_deviation = DEFAULT_JUNCTION_DEVIATION; + settings.arc_tolerance = DEFAULT_ARC_TOLERANCE; - settings.flags = 0; - if (DEFAULT_REPORT_INCHES) { settings.flags |= BITFLAG_REPORT_INCHES; } - if (DEFAULT_INVERT_ST_ENABLE) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; } - if (DEFAULT_INVERT_LIMIT_PINS) { settings.flags |= BITFLAG_INVERT_LIMIT_PINS; } - if (DEFAULT_SOFT_LIMIT_ENABLE) { settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; } - if (DEFAULT_HARD_LIMIT_ENABLE) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; } - if (DEFAULT_HOMING_ENABLE) { settings.flags |= BITFLAG_HOMING_ENABLE; } - - settings.steps_per_mm[X_AXIS] = DEFAULT_X_STEPS_PER_MM; - settings.steps_per_mm[Y_AXIS] = DEFAULT_Y_STEPS_PER_MM; - settings.steps_per_mm[Z_AXIS] = DEFAULT_Z_STEPS_PER_MM; - settings.max_rate[X_AXIS] = DEFAULT_X_MAX_RATE; - settings.max_rate[Y_AXIS] = DEFAULT_Y_MAX_RATE; - settings.max_rate[Z_AXIS] = DEFAULT_Z_MAX_RATE; - settings.acceleration[X_AXIS] = DEFAULT_X_ACCELERATION; - settings.acceleration[Y_AXIS] = DEFAULT_Y_ACCELERATION; - settings.acceleration[Z_AXIS] = DEFAULT_Z_ACCELERATION; - 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); + settings.rpm_max = DEFAULT_SPINDLE_RPM_MAX; + settings.rpm_min = DEFAULT_SPINDLE_RPM_MIN; - write_global_settings(); + settings.homing_dir_mask = DEFAULT_HOMING_DIR_MASK; + settings.homing_feed_rate = DEFAULT_HOMING_FEED_RATE; + settings.homing_seek_rate = DEFAULT_HOMING_SEEK_RATE; + settings.homing_debounce_delay = DEFAULT_HOMING_DEBOUNCE_DELAY; + settings.homing_pulloff = DEFAULT_HOMING_PULLOFF; + + settings.flags = 0; + if (DEFAULT_REPORT_INCHES) { settings.flags |= BITFLAG_REPORT_INCHES; } + if (DEFAULT_LASER_MODE) { settings.flags |= BITFLAG_LASER_MODE; } + if (DEFAULT_INVERT_ST_ENABLE) { settings.flags |= BITFLAG_INVERT_ST_ENABLE; } + if (DEFAULT_HARD_LIMIT_ENABLE) { settings.flags |= BITFLAG_HARD_LIMIT_ENABLE; } + if (DEFAULT_HOMING_ENABLE) { settings.flags |= BITFLAG_HOMING_ENABLE; } + if (DEFAULT_SOFT_LIMIT_ENABLE) { settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; } + if (DEFAULT_INVERT_LIMIT_PINS) { settings.flags |= BITFLAG_INVERT_LIMIT_PINS; } + if (DEFAULT_INVERT_PROBE_PIN) { settings.flags |= BITFLAG_INVERT_PROBE_PIN; } + + settings.steps_per_mm[X_AXIS] = DEFAULT_X_STEPS_PER_MM; + settings.steps_per_mm[Y_AXIS] = DEFAULT_Y_STEPS_PER_MM; + settings.steps_per_mm[Z_AXIS] = DEFAULT_Z_STEPS_PER_MM; + settings.max_rate[X_AXIS] = DEFAULT_X_MAX_RATE; + settings.max_rate[Y_AXIS] = DEFAULT_Y_MAX_RATE; + settings.max_rate[Z_AXIS] = DEFAULT_Z_MAX_RATE; + settings.acceleration[X_AXIS] = DEFAULT_X_ACCELERATION; + settings.acceleration[Y_AXIS] = DEFAULT_Y_ACCELERATION; + settings.acceleration[Z_AXIS] = DEFAULT_Z_ACCELERATION; + 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(); } - + if (restore_flag & SETTINGS_RESTORE_PARAMETERS) { - uint8_t idx; - float coord_data[N_AXIS]; - memset(&coord_data, 0, sizeof(coord_data)); - for (idx=0; idx <= SETTING_INDEX_NCOORD; idx++) { settings_write_coord_data(idx, coord_data); } + uint8_t idx; + float coord_data[N_AXIS]; + memset(&coord_data, 0, sizeof(coord_data)); + for (idx=0; idx <= SETTING_INDEX_NCOORD; idx++) { settings_write_coord_data(idx, coord_data); } } - + if (restore_flag & SETTINGS_RESTORE_STARTUP_LINES) { - #if N_STARTUP_LINE > 0 - eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK, 0); - #endif - #if N_STARTUP_LINE > 1 - eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK+(LINE_BUFFER_SIZE+1), 0); - #endif + #if N_STARTUP_LINE > 0 + eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK, 0); + eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK+1, 0); // Checksum + #endif + #if N_STARTUP_LINE > 1 + eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK+(LINE_BUFFER_SIZE+1), 0); + eeprom_put_char(EEPROM_ADDR_STARTUP_BLOCK+(LINE_BUFFER_SIZE+2), 0); // Checksum + #endif + } + + if (restore_flag & SETTINGS_RESTORE_BUILD_INFO) { + eeprom_put_char(EEPROM_ADDR_BUILD_INFO , 0); + eeprom_put_char(EEPROM_ADDR_BUILD_INFO+1 , 0); // Checksum } - - if (restore_flag & SETTINGS_RESTORE_BUILD_INFO) { eeprom_put_char(EEPROM_ADDR_BUILD_INFO , 0); } } @@ -152,12 +170,12 @@ uint8_t settings_read_coord_data(uint8_t coord_select, float *coord_data) uint32_t addr = coord_select*(sizeof(float)*N_AXIS+1) + EEPROM_ADDR_PARAMETERS; if (!(memcpy_from_eeprom_with_checksum((char*)coord_data, addr, sizeof(float)*N_AXIS))) { // Reset with default zero vector - clear_vector_float(coord_data); + clear_vector_float(coord_data); settings_write_coord_data(coord_select,coord_data); return(false); } return(true); -} +} // Reads Grbl global settings struct from EEPROM. @@ -170,7 +188,7 @@ uint8_t read_global_settings() { return(false); } } else { - return(false); + return(false); } return(true); } @@ -178,7 +196,7 @@ uint8_t read_global_settings() { // A helper method to set settings from command line uint8_t settings_store_global_setting(uint8_t parameter, float value) { - if (value < 0.0) { return(STATUS_NEGATIVE_VALUE); } + if (value < 0.0) { return(STATUS_NEGATIVE_VALUE); } if (parameter >= AXIS_SETTINGS_START_VAL) { // Store axis configuration. Axis numbering sequence set by AXIS_SETTING defines. // NOTE: Ensure the setting index corresponds to the report.c settings printout. @@ -215,16 +233,16 @@ uint8_t settings_store_global_setting(uint8_t parameter, float value) { // Store non-axis Grbl settings uint8_t int_value = trunc(value); switch(parameter) { - case 0: + case 0: if (int_value < 3) { return(STATUS_SETTING_STEP_PULSE_MIN); } settings.pulse_microseconds = int_value; break; case 1: settings.stepper_idle_lock_time = int_value; break; - case 2: - settings.step_invert_mask = int_value; + case 2: + settings.step_invert_mask = int_value; st_generate_step_dir_invert_masks(); // Regenerate step and direction port invert masks. break; - case 3: - settings.dir_invert_mask = int_value; + case 3: + settings.dir_invert_mask = int_value; st_generate_step_dir_invert_masks(); // Regenerate step and direction port invert masks. break; case 4: // Reset to ensure change. Immediate re-init may cause problems. @@ -238,6 +256,7 @@ uint8_t settings_store_global_setting(uint8_t parameter, float value) { case 6: // Reset to ensure change. Immediate re-init may cause problems. if (int_value) { settings.flags |= BITFLAG_INVERT_PROBE_PIN; } else { settings.flags &= ~BITFLAG_INVERT_PROBE_PIN; } + probe_configure_invert_mask(false); break; case 10: settings.status_report_mask = int_value; break; case 11: settings.junction_deviation = value; break; @@ -247,9 +266,9 @@ uint8_t settings_store_global_setting(uint8_t parameter, float value) { else { settings.flags &= ~BITFLAG_REPORT_INCHES; } break; case 20: - if (int_value) { + if (int_value) { if (bit_isfalse(settings.flags, BITFLAG_HOMING_ENABLE)) { return(STATUS_SOFT_LIMIT_ERROR); } - settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; + settings.flags |= BITFLAG_SOFT_LIMIT_ENABLE; } else { settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; } break; case 21: @@ -259,8 +278,8 @@ uint8_t settings_store_global_setting(uint8_t parameter, float value) { break; case 22: if (int_value) { settings.flags |= BITFLAG_HOMING_ENABLE; } - else { - settings.flags &= ~BITFLAG_HOMING_ENABLE; + else { + settings.flags &= ~BITFLAG_HOMING_ENABLE; settings.flags &= ~BITFLAG_SOFT_LIMIT_ENABLE; // Force disable soft-limits. } break; @@ -271,7 +290,15 @@ uint8_t settings_store_global_setting(uint8_t parameter, float value) { case 27: settings.homing_pulloff = value; break; case 30: settings.rpm_max = value; break; case 31: settings.rpm_min = value; break; - default: + case 32: + #ifdef VARIABLE_SPINDLE + if (int_value) { settings.flags |= BITFLAG_LASER_MODE; } + else { settings.flags &= ~BITFLAG_LASER_MODE; } + #else + return(STATUS_SETTING_DISABLED); + #endif + break; + default: return(STATUS_INVALID_STATEMENT); } } @@ -287,18 +314,6 @@ void settings_init() { settings_restore(SETTINGS_RESTORE_ALL); // Force restore all EEPROM data. report_grbl_settings(); } - - // NOTE: Checking paramater data, startup lines, and build info string should be done here, - // but it seems fairly redundant. Each of these can be manually checked and reset or restored. - // Check all parameter data into a dummy variable. If error, reset to zero, otherwise do nothing. - // float coord_data[N_AXIS]; - // uint8_t i; - // for (i=0; i<=SETTING_INDEX_NCOORD; i++) { - // if (!settings_read_coord_data(i, coord_data)) { - // report_status_message(STATUS_SETTING_READ_FAIL); - // } - // } - // NOTE: Startup lines are checked and executed by protocol_main_loop at the end of initialization. } diff --git a/grbl/settings.h b/grbl/settings.h index 3a9d25d..bd353d5 100644 --- a/grbl/settings.h +++ b/grbl/settings.h @@ -1,10 +1,10 @@ /* - settings.h - eeprom configuration handling + settings.h - eeprom configuration handling Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC 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 @@ -31,7 +31,7 @@ // Define bit flag masks for the boolean settings in settings.flag. #define BITFLAG_REPORT_INCHES bit(0) -// #define BITFLAG_AUTO_START bit(1) // Obsolete. Don't alter to keep back compatibility. +#define BITFLAG_LASER_MODE bit(1) #define BITFLAG_INVERT_ST_ENABLE bit(2) #define BITFLAG_HARD_LIMIT_ENABLE bit(3) #define BITFLAG_HOMING_ENABLE bit(4) @@ -40,24 +40,31 @@ #define BITFLAG_INVERT_PROBE_PIN bit(7) // Define status reporting boolean enable bit flags in settings.status_report_mask -#define BITFLAG_RT_STATUS_MACHINE_POSITION bit(0) -#define BITFLAG_RT_STATUS_WORK_POSITION bit(1) -#define BITFLAG_RT_STATUS_PLANNER_BUFFER bit(2) -#define BITFLAG_RT_STATUS_SERIAL_RX bit(3) -#define BITFLAG_RT_STATUS_LIMIT_PINS bit(4) -#define BITFLAG_RT_STATUS_PROBE_PIN bit(5) -#define BITFLAG_RT_STATUS_CONTROL_PINS bit(6) +#ifdef USE_CLASSIC_REALTIME_REPORT + #define BITFLAG_RT_STATUS_MACHINE_POSITION bit(0) + #define BITFLAG_RT_STATUS_WORK_POSITION bit(1) + #define BITFLAG_RT_STATUS_PLANNER_BUFFER bit(2) + #define BITFLAG_RT_STATUS_SERIAL_RX bit(3) + #define BITFLAG_RT_STATUS_LIMIT_PINS bit(4) + #define BITFLAG_RT_STATUS_PROBE_PIN bit(5) + #define BITFLAG_RT_STATUS_CONTROL_PINS bit(6) + #define BITFLAG_RT_STATUS_OVERRIDES bit(7) +#else + #define BITFLAG_RT_STATUS_POSITION_TYPE bit(0) +#endif // Define settings restore bitflags. -#define SETTINGS_RESTORE_ALL 0xFF // All bitflags #define SETTINGS_RESTORE_DEFAULTS bit(0) #define SETTINGS_RESTORE_PARAMETERS bit(1) #define SETTINGS_RESTORE_STARTUP_LINES bit(2) #define SETTINGS_RESTORE_BUILD_INFO bit(3) +#ifndef SETTINGS_RESTORE_ALL + #define SETTINGS_RESTORE_ALL 0xFF // All bitflags +#endif // 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 -// the startup script. The lower half contains the global settings and space for future +// the startup script. The lower half contains the global settings and space for future // developments. #define EEPROM_ADDR_GLOBAL 1U #define EEPROM_ADDR_PARAMETERS 512U @@ -93,10 +100,10 @@ typedef struct { uint8_t status_report_mask; // Mask to indicate desired report data. float junction_deviation; float arc_tolerance; - + float rpm_max; float rpm_min; - + uint8_t flags; // Contains default boolean settings uint8_t homing_dir_mask; diff --git a/grbl/spindle_control.c b/grbl/spindle_control.c index 6bd85e0..8ae2278 100644 --- a/grbl/spindle_control.c +++ b/grbl/spindle_control.c @@ -2,7 +2,7 @@ spindle_control.c - spindle control methods Part of Grbl - Copyright (c) 2012-2015 Sungeun K. Jeon + Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -22,8 +22,20 @@ #include "grbl.h" +#ifdef SPINDLE_MINIMUM_PWM + #define SPINDLE_PWM_MIN_VALUE SPINDLE_MINIMUM_PWM +#else + #define SPINDLE_PWM_MIN_VALUE 0 +#endif +#define SPINDLE_PWM_RANGE (SPINDLE_PWM_MAX_VALUE-SPINDLE_PWM_MIN_VALUE) + +#ifdef VARIABLE_SPINDLE + static float pwm_gradient; // Precalulated value to speed up rpm to PWM conversions. +#endif + + void spindle_init() -{ +{ #ifdef VARIABLE_SPINDLE // Configure variable spindle PWM and enable pin, if requried. On the Uno, PWM and enable are @@ -33,28 +45,31 @@ void spindle_init() SPINDLE_TCCRB_REGISTER = SPINDLE_TCCRB_INIT_MASK; #ifdef USE_SPINDLE_DIR_AS_ENABLE_PIN SPINDLE_ENABLE_DDR |= (1<= settings.rpm_max) || (rpm > settings.rpm_max)) { + // No PWM range possible. Set simple on/off spindle control pin state. + return(SPINDLE_PWM_MAX_VALUE); + } else if (rpm < settings.rpm_min) { + if (rpm == 0.0) { return(SPINDLE_PWM_OFF_VALUE); } + else { return(SPINDLE_PWM_MIN_VALUE); } + } else { + return(floor( (rpm-settings.rpm_min)*pwm_gradient + (SPINDLE_PWM_MIN_VALUE+0.5))); + } + } +#endif + + +// Immediately sets spindle running state with direction and spindle rpm via PWM, if enabled. +// Called by spindle_run() after sync and parking motion/spindle stop override during restore. +void spindle_set_state(uint8_t state, uint8_t pwm_value) { if (sys.abort) { return; } // Block during abort. - - // Halt or set spindle direction and rpm. + + // Halt or set spindle direction and rpm. if (state == SPINDLE_DISABLE) { spindle_stop(); @@ -92,59 +146,32 @@ void spindle_set_state(uint8_t state, float rpm) #ifdef VARIABLE_SPINDLE - // TODO: Install the optional capability for frequency-based output for servos. - uint8_t current_pwm; // 328p PWM register is 8-bit. + spindle_set_speed(pwm_value); - // Calculate PWM register value based on rpm max/min settings and programmed rpm. - if (rpm <= 0.0) { spindle_stop(); } // RPM should never be negative, but check anyway. - else { - if (settings.rpm_max <= settings.rpm_min) { - // No PWM range possible. Set simple on/off spindle control pin state. - current_pwm = SPINDLE_PWM_MAX_VALUE; - } else { - if (rpm > settings.rpm_max) { rpm = settings.rpm_max; } - if (rpm < settings.rpm_min) { rpm = settings.rpm_min; } - #ifdef SPINDLE_MINIMUM_PWM - float pwm_gradient = (SPINDLE_PWM_MAX_VALUE-SPINDLE_MINIMUM_PWM)/(settings.rpm_max-settings.rpm_min); - current_pwm = floor( (rpm-settings.rpm_min)*pwm_gradient + (SPINDLE_MINIMUM_PWM+0.5)); - #else - float pwm_gradient = (SPINDLE_PWM_MAX_VALUE)/(settings.rpm_max-settings.rpm_min); - current_pwm = floor( (rpm-settings.rpm_min)*pwm_gradient + 0.5); - #endif - } - - SPINDLE_OCR_REGISTER = current_pwm; // Set PWM output level. - SPINDLE_TCCRA_REGISTER |= (1<. -*/ +*/ #ifndef spindle_control_h -#define spindle_control_h +#define spindle_control_h // Initializes spindle pins and hardware PWM, if enabled. void spindle_init(); -// Sets spindle direction and spindle rpm via PWM, if enabled. +// Called by g-code parser when setting spindle state and requires a buffer sync. void spindle_run(uint8_t direction, float rpm); -void spindle_set_state(uint8_t state, float rpm); +// Immediately sets spindle running state with direction and spindle rpm via PWM, if enabled. +// Called by spindle_run() after sync and parking motion/spindle stop override during restore. +void spindle_set_state(uint8_t state, uint8_t pwm_value); + +// Stop and start spindle routines. Called by all spindle routines and stepper ISR. +inline void spindle_stop(); +inline void spindle_set_speed(uint8_t pwm_value); // Variable spindle only. + +uint8_t spindle_compute_pwm_value(float rpm); // 328p PWM register is 8-bit. Variable spindle only. -// Kills spindle. -void spindle_stop(); #endif diff --git a/grbl/stepper.c b/grbl/stepper.c index 4c181c1..1b281bf 100644 --- a/grbl/stepper.c +++ b/grbl/stepper.c @@ -2,9 +2,9 @@ stepper.c - stepper motor driver: executes motion plans using stepper motors Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC 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 @@ -23,21 +23,23 @@ // Some useful constants. -#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment -#define REQ_MM_INCREMENT_SCALAR 1.25 +#define DT_SEGMENT (1.0/(ACCELERATION_TICKS_PER_SECOND*60.0)) // min/segment +#define REQ_MM_INCREMENT_SCALAR 1.25 #define RAMP_ACCEL 0 #define RAMP_CRUISE 1 #define RAMP_DECEL 2 +#define RAMP_DECEL_OVERRIDE 3 #define PREP_FLAG_RECALCULATE bit(0) #define PREP_FLAG_HOLD_PARTIAL_BLOCK bit(1) #define PREP_FLAG_PARKING bit(2) +#define PREP_FLAG_DECEL_OVERRIDE bit(3) // Define Adaptive Multi-Axis Step-Smoothing(AMASS) levels and cutoff frequencies. The highest level // frequency bin starts at 0Hz and ends at its cutoff frequency. The next lower level frequency bin // starts at the next higher cutoff frequency, and so on. The cutoff frequencies for each level must // be considered carefully against how much it over-drives the stepper ISR, the accuracy of the 16-bit -// timer, and the CPU overhead. Level 0 (no AMASS, normal operation) frequency bin starts at the +// timer, and the CPU overhead. Level 0 (no AMASS, normal operation) frequency bin starts at the // Level 1 cutoff frequency and up to as fast as the CPU allows (over 30kHz in limited testing). // NOTE: AMASS cutoff frequency multiplied by ISR overdrive factor must not exceed maximum step frequency. // NOTE: Current settings are set to overdrive the ISR to no more than 16kHz, balancing CPU overhead @@ -49,22 +51,25 @@ #define AMASS_LEVEL3 (F_CPU/2000) // Over-drives ISR (x8) -// Stores the planner block Bresenham algorithm execution data for the segments in the segment +// Stores the planner block Bresenham algorithm execution data for the segments in the segment // buffer. 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). // NOTE: This data is copied from the prepped planner blocks so that the planner blocks may be // discarded when entirely consumed and completed by the segment buffer. Also, AMASS alters this -// data for its own use. -typedef struct { +// data for its own use. +typedef struct { uint8_t direction_bits; + #ifdef VARIABLE_SPINDLE + uint8_t spindle_pwm; + #endif uint32_t steps[N_AXIS]; uint32_t step_event_count; } st_block_t; static st_block_t st_block_buffer[SEGMENT_BUFFER_SIZE-1]; -// Primary stepper segment ring buffer. Contains small, short line segments for the stepper +// 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 +// 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 { uint16_t n_step; // Number of step events to be executed for this segment @@ -82,12 +87,12 @@ static segment_t segment_buffer[SEGMENT_BUFFER_SIZE]; typedef struct { // Used by the bresenham line algorithm uint32_t counter_x, // Counter variables for the bresenham line tracer - counter_y, + counter_y, counter_z; #ifdef STEP_PULSE_DELAY uint8_t step_bits; // Stores out_bits output to complete the step pulse delay #endif - + uint8_t execute_step; // Flags step execution for each interrupt. uint8_t step_pulse_time; // Step pulse reset time after step rise uint8_t step_outbits; // The next stepping-bits to be output @@ -96,7 +101,7 @@ typedef struct { uint32_t steps[N_AXIS]; #endif - uint16_t step_count; // Steps remaining in line segment motion + uint16_t step_count; // Steps remaining in line segment motion uint8_t exec_block_index; // Tracks the current st_block index. Change indicates new block. st_block_t *exec_block; // Pointer to the block data for the segment being executed segment_t *exec_segment; // Pointer to the segment being executed @@ -108,24 +113,24 @@ static volatile uint8_t segment_buffer_tail; static uint8_t segment_buffer_head; static uint8_t segment_next_head; -// Step and direction port invert masks. +// Step and direction port invert masks. static uint8_t step_port_invert_mask; static uint8_t dir_port_invert_mask; // Used to avoid ISR nesting of the "Stepper Driver Interrupt". Should never occur though. -static volatile uint8_t busy; +static volatile uint8_t busy; // 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_block; // Pointer to the planner block being prepped -static st_block_t *st_prep_block; // Pointer to the stepper block data being prepped +static st_block_t *st_prep_block; // Pointer to the stepper block data being prepped // Segment preparation data struct. Contains all the necessary information to compute new segments // based on the current executing planner block. typedef struct { uint8_t st_block_index; // Index of stepper common data block being prepped uint8_t recalculate_flag; - + float dt_remainder; float steps_remaining; float step_per_mm; @@ -150,7 +155,7 @@ typedef struct { static st_prep_t prep; -/* BLOCK VELOCITY PROFILE DEFINITION +/* BLOCK VELOCITY PROFILE DEFINITION __________________________ /| |\ _________________ ^ / | | \ /| |\ | @@ -161,72 +166,70 @@ static st_prep_t prep; | BLOCK 1 ^ BLOCK 2 | d | time -----> EXAMPLE: Block 2 entry speed is at max junction velocity - + The planner block buffer is planned assuming constant acceleration velocity profiles and are continuously joined at block junctions as shown above. However, the planner only actively computes the block entry speeds for an optimal velocity plan, but does not compute the block internal - velocity profiles. These velocity profiles are computed ad-hoc as they are executed by the + velocity profiles. These velocity profiles are computed ad-hoc as they are executed by the stepper algorithm and consists of only 7 possible types of profiles: cruise-only, cruise- - deceleration, acceleration-cruise, acceleration-only, deceleration-only, full-trapezoid, and + deceleration, acceleration-cruise, acceleration-only, deceleration-only, full-trapezoid, and triangle(no cruise). - maximum_speed (< nominal_speed) -> + - +--------+ <- maximum_speed (= nominal_speed) /|\ - / \ / | \ + maximum_speed (< nominal_speed) -> + + +--------+ <- maximum_speed (= nominal_speed) /|\ + / \ / | \ current_speed -> + \ / | + <- exit_speed - | + <- exit_speed / | | - +-------------+ current_speed -> +----+--+ - time --> ^ ^ ^ ^ - | | | | + | + <- exit_speed / | | + +-------------+ current_speed -> +----+--+ + time --> ^ ^ ^ ^ + | | | | decelerate_after(in mm) decelerate_after(in mm) ^ ^ ^ ^ | | | | accelerate_until(in mm) accelerate_until(in mm) - + The step segment buffer computes the executing block velocity profile and tracks the critical - parameters for the stepper algorithm to accurately trace the profile. These critical parameters + parameters for the stepper algorithm to accurately trace the profile. These critical parameters are shown and defined in the above illustration. */ // 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() +void st_wake_up() { // Enable stepper drivers. if (bit_istrue(settings.flags,BITFLAG_INVERT_ST_ENABLE)) { STEPPERS_DISABLE_PORT |= (1<> 3); - // Set delay between direction pin write and step command. - OCR0A = -(((settings.pulse_microseconds)*TICKS_PER_MICROSECOND) >> 3); - #else // Normal operation - // Set step pulse time. Ad hoc computation from oscilloscope. Uses two's complement. - st.step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3); - #endif + // Initialize stepper output bits + st.dir_outbits = dir_port_invert_mask; + st.step_outbits = step_port_invert_mask; - // Enable Stepper Driver Interrupt - TIMSK1 |= (1<> 3); + // Set delay between direction pin write and step command. + OCR0A = -(((settings.pulse_microseconds)*TICKS_PER_MICROSECOND) >> 3); + #else // Normal operation + // Set step pulse time. Ad hoc computation from oscilloscope. Uses two's complement. + st.step_pulse_time = -(((settings.pulse_microseconds-2)*TICKS_PER_MICROSECOND) >> 3); + #endif + + // Enable Stepper Driver Interrupt + TIMSK1 |= (1<st_block_index ) { st.exec_block_index = st.exec_segment->st_block_index; st.exec_block = &st_block_buffer[st.exec_block_index]; - + // Initialize Bresenham line and distance counters st.counter_x = st.counter_y = st.counter_z = (st.exec_block->step_event_count >> 1); } - st.dir_outbits = st.exec_block->direction_bits ^ dir_port_invert_mask; + st.dir_outbits = st.exec_block->direction_bits ^ dir_port_invert_mask; #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING // With AMASS enabled, adjust Bresenham axis increment counters according to AMASS level. @@ -345,68 +348,73 @@ ISR(TIMER1_COMPA_vect) st.steps[Y_AXIS] = st.exec_block->steps[Y_AXIS] >> st.exec_segment->amass_level; st.steps[Z_AXIS] = st.exec_block->steps[Z_AXIS] >> st.exec_segment->amass_level; #endif - + + #ifdef VARIABLE_SPINDLE + // Set real-time spindle output as segment is loaded, just prior to the first step. + spindle_set_speed(st.exec_block->spindle_pwm); + #endif + } else { // Segment buffer empty. Shutdown. st_go_idle(); system_set_exec_state_flag(EXEC_CYCLE_STOP); // Flag main program for cycle end return; // Nothing to do but exit. - } + } } - - + + // Check probing state. - probe_state_monitor(); - + if (sys_probe_state == PROBE_ACTIVE) { probe_state_monitor(); } + // Reset step out bits. - st.step_outbits = 0; + st.step_outbits = 0; // Execute step displacement profile by Bresenham line algorithm #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING st.counter_x += st.steps[X_AXIS]; #else st.counter_x += st.exec_block->steps[X_AXIS]; - #endif + #endif if (st.counter_x > st.exec_block->step_event_count) { st.step_outbits |= (1<step_event_count; - if (st.exec_block->direction_bits & (1<direction_bits & (1<steps[Y_AXIS]; - #endif + #endif if (st.counter_y > st.exec_block->step_event_count) { st.step_outbits |= (1<step_event_count; - if (st.exec_block->direction_bits & (1<direction_bits & (1<steps[Z_AXIS]; - #endif + #endif if (st.counter_z > st.exec_block->step_event_count) { st.step_outbits |= (1<step_event_count; - if (st.exec_block->direction_bits & (1<direction_bits & (1<entry_speed_sqr = prep.current_speed*prep.current_speed; // Update entry speed. @@ -545,10 +553,9 @@ static uint8_t st_next_block_index(uint8_t block_index) // Restores the step segment buffer to the normal run state after a parking motion. - // NOTE: This function does not compile if parking is disabled. void st_parking_restore_buffer() - { - // Restore step execution data and flags of partially completed block, if necessary. + { + // Restore step execution data and flags of partially completed block, if necessary. if (prep.recalculate_flag & PREP_FLAG_HOLD_PARTIAL_BLOCK) { st_prep_block = &st_block_buffer[prep.last_st_block_index]; prep.st_block_index = prep.last_st_block_index; @@ -565,7 +572,7 @@ static uint8_t st_next_block_index(uint8_t block_index) #endif -/* Prepares step segment buffer. Continuously called from main program. +/* 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 @@ -574,7 +581,7 @@ static uint8_t st_next_block_index(uint8_t block_index) 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. + longer than the time it takes the stepper algorithm to empty it before refilling it. Currently, the segment buffer conservatively holds roughly up to 40-50 msec of steps. NOTE: Computation units are in steps, millimeters, and minutes. */ @@ -588,42 +595,29 @@ void st_prep_buffer() // Determine if we need to load a new planner block or if the block needs to be recomputed. if (pl_block == NULL) { - #ifdef PARKING_ENABLE - - // Query planner for a queued block - if (sys.step_control & STEP_CONTROL_EXECUTE_PARK) { pl_block = plan_get_parking_block(); } - else { pl_block = plan_get_current_block(); } - if (pl_block == NULL) { return; } // No planner blocks. Exit. + // Query planner for a queued block + if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { pl_block = plan_get_system_motion_block(); } + else { pl_block = plan_get_current_block(); } + if (pl_block == NULL) { return; } // No planner blocks. Exit. - // Check if we need to only recompute the velocity profile or load a new block. - if (prep.recalculate_flag & PREP_FLAG_RECALCULATE) { + // Check if we need to only recompute the velocity profile or load a new block. + if (prep.recalculate_flag & PREP_FLAG_RECALCULATE) { + #ifdef PARKING_ENABLE if (prep.recalculate_flag & PREP_FLAG_PARKING) { prep.recalculate_flag &= ~(PREP_FLAG_RECALCULATE); } else { prep.recalculate_flag = false; } - - } else { - - #else - - // Query planner for a queued block - pl_block = plan_get_current_block(); - if (pl_block == NULL) { return; } // No planner blocks. Exit. - - // Check if we need to only recompute the velocity profile or load a new block. - if (prep.recalculate_flag & PREP_FLAG_RECALCULATE) { - + #else prep.recalculate_flag = false; - - } else { - - #endif - + #endif + + } else { + // Load the Bresenham stepping data for the block. prep.st_block_index = st_next_block_index(prep.st_block_index); - + // Prepare and copy Bresenham algorithm segment data from the new planner block, so that - // when the segment buffer completes the planner block, it may be discarded when the - // segment buffer finishes the prepped block, but the stepper ISR is still executing it. + // when the segment buffer completes the planner block, it may be discarded when the + // segment buffer finishes the prepped block, but the stepper ISR is still executing it. st_prep_block = &st_block_buffer[prep.st_block_index]; st_prep_block->direction_bits = pl_block->direction_bits; uint8_t idx; @@ -631,32 +625,33 @@ void st_prep_buffer() for (idx=0; idxsteps[idx] = pl_block->steps[idx]; } st_prep_block->step_event_count = pl_block->step_event_count; #else - // With AMASS enabled, simply bit-shift multiply all Bresenham data by the max AMASS + // With AMASS enabled, simply bit-shift multiply all Bresenham data by the max AMASS // level, such that we never divide beyond the original data anywhere in the algorithm. // If the original data is divided, we can lose a step from integer roundoff. for (idx=0; idxsteps[idx] = pl_block->steps[idx] << MAX_AMASS_LEVEL; } st_prep_block->step_event_count = pl_block->step_event_count << MAX_AMASS_LEVEL; #endif - + // Initialize segment buffer data for generating the segments. prep.steps_remaining = (float)pl_block->step_event_count; prep.step_per_mm = prep.steps_remaining/pl_block->millimeters; prep.req_mm_increment = REQ_MM_INCREMENT_SCALAR/prep.step_per_mm; prep.dt_remainder = 0.0; // Reset for new segment block - - if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { + + if ((sys.step_control & STEP_CONTROL_EXECUTE_HOLD) || (prep.recalculate_flag & PREP_FLAG_DECEL_OVERRIDE)) { // New block loaded mid-hold. Override planner block entry speed to enforce deceleration. - prep.current_speed = prep.exit_speed; + prep.current_speed = prep.exit_speed; pl_block->entry_speed_sqr = prep.exit_speed*prep.exit_speed; - } else { - prep.current_speed = sqrt(pl_block->entry_speed_sqr); + prep.recalculate_flag &= ~(PREP_FLAG_DECEL_OVERRIDE); + } else { + prep.current_speed = sqrt(pl_block->entry_speed_sqr); } } - - /* --------------------------------------------------------------------------------- + + /* --------------------------------------------------------------------------------- Compute the velocity profile of a new planner block based on its entry and exit - speeds, or recompute the profile of a partially-completed planner block if the - planner has updated it. For a commanded forced-deceleration, such as from a feed + speeds, or recompute the profile of a partially-completed planner block if the + planner has updated it. For a commanded forced-deceleration, such as from a feed hold, override the planner velocities and decelerate to the target exit speed. */ prep.mm_complete = 0.0; // Default velocity profile complete at 0.0mm from end of block. @@ -677,47 +672,77 @@ void st_prep_buffer() } else { // [Normal Operation] // Compute or recompute velocity profile parameters of the prepped planner block. prep.ramp_type = RAMP_ACCEL; // Initialize as acceleration ramp. - prep.accelerate_until = pl_block->millimeters; + prep.accelerate_until = pl_block->millimeters; - #ifdef PARKING_ENABLE - if (sys.step_control & STEP_CONTROL_EXECUTE_PARK) { prep.exit_speed = 0.0; } - else { prep.exit_speed = plan_get_exec_block_exit_speed(); } - #else - prep.exit_speed = plan_get_exec_block_exit_speed(); - #endif + float exit_speed_sqr; + float nominal_speed; + if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { + prep.exit_speed = exit_speed_sqr = 0.0; // Enforce stop at end of system motion. + } else { + exit_speed_sqr = plan_get_exec_block_exit_speed_sqr(); + prep.exit_speed = sqrt(exit_speed_sqr); + } - float exit_speed_sqr = prep.exit_speed*prep.exit_speed; + nominal_speed = plan_compute_profile_nominal_speed(pl_block); + float nominal_speed_sqr = nominal_speed*nominal_speed; float intersect_distance = 0.5*(pl_block->millimeters+inv_2_accel*(pl_block->entry_speed_sqr-exit_speed_sqr)); - if (intersect_distance > 0.0) { + + if (pl_block->entry_speed_sqr > nominal_speed_sqr) { // Only occurs during override reductions. + prep.accelerate_until = pl_block->millimeters - inv_2_accel*(pl_block->entry_speed_sqr-nominal_speed_sqr); + if (prep.accelerate_until <= 0.0) { // Deceleration-only. + prep.ramp_type = RAMP_DECEL; + // prep.decelerate_after = pl_block->millimeters; + // prep.maximum_speed = prep.current_speed; + + // Compute override block exit speed since it doesn't match the planner exit speed. + prep.exit_speed = sqrt(pl_block->entry_speed_sqr - 2*pl_block->acceleration*pl_block->millimeters); + prep.recalculate_flag |= PREP_FLAG_DECEL_OVERRIDE; // Flag to load next block as deceleration override. + + // TODO: Determine correct handling of parameters in deceleration-only. + // Can be tricky since entry speed will be current speed, as in feed holds. + // Also, look into near-zero speed handling issues with this. + + } else { + // Decelerate to cruise or cruise-decelerate types. Guaranteed to intersect updated plan. + prep.decelerate_after = inv_2_accel*(nominal_speed_sqr-exit_speed_sqr); + prep.maximum_speed = nominal_speed; + prep.ramp_type = RAMP_DECEL_OVERRIDE; + } + } else if (intersect_distance > 0.0) { if (intersect_distance < pl_block->millimeters) { // Either trapezoid or triangle types // NOTE: For acceleration-cruise and cruise-only types, following calculation will be 0.0. - prep.decelerate_after = inv_2_accel*(pl_block->nominal_speed_sqr-exit_speed_sqr); + prep.decelerate_after = inv_2_accel*(nominal_speed_sqr-exit_speed_sqr); if (prep.decelerate_after < intersect_distance) { // Trapezoid type - prep.maximum_speed = sqrt(pl_block->nominal_speed_sqr); - if (pl_block->entry_speed_sqr == pl_block->nominal_speed_sqr) { + prep.maximum_speed = nominal_speed; + if (pl_block->entry_speed_sqr == nominal_speed_sqr) { // Cruise-deceleration or cruise-only type. prep.ramp_type = RAMP_CRUISE; } else { // Full-trapezoid or acceleration-cruise types - prep.accelerate_until -= inv_2_accel*(pl_block->nominal_speed_sqr-pl_block->entry_speed_sqr); + prep.accelerate_until -= inv_2_accel*(nominal_speed_sqr-pl_block->entry_speed_sqr); } } else { // Triangle type prep.accelerate_until = intersect_distance; prep.decelerate_after = intersect_distance; prep.maximum_speed = sqrt(2.0*pl_block->acceleration*intersect_distance+exit_speed_sqr); - } + } } else { // Deceleration-only type - prep.ramp_type = RAMP_DECEL; - // prep.decelerate_after = pl_block->millimeters; - // prep.maximum_speed = prep.current_speed; + prep.ramp_type = RAMP_DECEL; + // prep.decelerate_after = pl_block->millimeters; + // prep.maximum_speed = prep.current_speed; } } else { // Acceleration-only type prep.accelerate_until = 0.0; // prep.decelerate_after = 0.0; prep.maximum_speed = prep.exit_speed; } - } + } + + #ifdef VARIABLE_SPINDLE + st_prep_block->spindle_pwm = spindle_compute_pwm_value((0.01*sys.spindle_speed_ovr)*pl_block->spindle_speed); + #endif + } // Initialize new segment @@ -728,30 +753,44 @@ void st_prep_buffer() /*------------------------------------------------------------------------------------ Compute the average velocity of this new segment by determining the total distance - traveled over the segment time DT_SEGMENT. The following code first attempts to create - a full segment based on the current ramp conditions. If the segment time is incomplete + traveled over the segment time DT_SEGMENT. The following code first attempts to create + a full segment based on the current ramp conditions. If the segment time is incomplete when terminating at a ramp state change, the code will continue to loop through the - progressing ramp states to fill the remaining segment execution time. However, if - an incomplete segment terminates at the end of the velocity profile, the segment is + progressing ramp states to fill the remaining segment execution time. However, if + an incomplete segment terminates at the end of the velocity profile, the segment is considered completed despite having a truncated execution time less than DT_SEGMENT. The velocity profile is always assumed to progress through the ramp sequence: acceleration ramp, cruising state, and deceleration ramp. Each ramp's travel distance - may range from zero to the length of the block. Velocity profiles can end either at - the end of planner block (typical) or mid-block at the end of a forced deceleration, + may range from zero to the length of the block. Velocity profiles can end either at + the end of planner block (typical) or mid-block at the end of a forced deceleration, such as from a feed hold. */ float dt_max = DT_SEGMENT; // Maximum segment time float dt = 0.0; // Initialize segment time float time_var = dt_max; // Time worker variable float mm_var; // mm-Distance worker variable - float speed_var; // Speed worker variable + float speed_var; // Speed worker variable float mm_remaining = pl_block->millimeters; // New segment distance from end of block. float minimum_mm = mm_remaining-prep.req_mm_increment; // Guarantee at least one step. if (minimum_mm < 0.0) { minimum_mm = 0.0; } do { switch (prep.ramp_type) { - case RAMP_ACCEL: + case RAMP_DECEL_OVERRIDE: + speed_var = pl_block->acceleration*time_var; + mm_var = time_var*(prep.current_speed - 0.5*speed_var); + mm_remaining -= mm_var; + if ((mm_remaining < prep.accelerate_until) || (mm_var <= 0)) { + // Cruise or cruise-deceleration types only for deceleration override. + mm_remaining = prep.accelerate_until; // NOTE: 0.0 at EOB + time_var = 2.0*(pl_block->millimeters-mm_remaining)/(prep.current_speed+prep.maximum_speed); + prep.ramp_type = RAMP_CRUISE; + prep.current_speed = prep.maximum_speed; + } else { // Mid-deceleration override ramp. + prep.current_speed -= speed_var; + } + break; + case RAMP_ACCEL: // NOTE: Acceleration ramp only computes during first do-while loop. speed_var = pl_block->acceleration*time_var; mm_remaining -= time_var*(prep.current_speed + 0.5*speed_var); @@ -762,23 +801,23 @@ void st_prep_buffer() if (mm_remaining == prep.decelerate_after) { prep.ramp_type = RAMP_DECEL; } else { prep.ramp_type = RAMP_CRUISE; } prep.current_speed = prep.maximum_speed; - } else { // Acceleration only. + } else { // Acceleration only. prep.current_speed += speed_var; } break; - case RAMP_CRUISE: + case RAMP_CRUISE: // NOTE: mm_var used to retain the last mm_remaining for incomplete segment time_var calculations. - // NOTE: If maximum_speed*time_var value is too low, round-off can cause mm_var to not change. To + // NOTE: If maximum_speed*time_var value is too low, round-off can cause mm_var to not change. To // prevent this, simply enforce a minimum speed threshold in the planner. mm_var = mm_remaining - prep.maximum_speed*time_var; - if (mm_var < prep.decelerate_after) { // End of cruise. + if (mm_var < prep.decelerate_after) { // End of cruise. // Cruise-deceleration junction or end of block. time_var = (mm_remaining - prep.decelerate_after)/prep.maximum_speed; mm_remaining = prep.decelerate_after; // NOTE: 0.0 at EOB prep.ramp_type = RAMP_DECEL; - } else { // Cruising only. - mm_remaining = mm_var; - } + } else { // Cruising only. + mm_remaining = mm_var; + } break; default: // case RAMP_DECEL: // NOTE: mm_var used as a misc worker variable to prevent errors when near zero speed. @@ -794,7 +833,7 @@ void st_prep_buffer() } // Otherwise, at end of block or end of forced-deceleration. time_var = 2.0*(mm_remaining-prep.mm_complete)/(prep.current_speed+prep.exit_speed); - mm_remaining = prep.mm_complete; + mm_remaining = prep.mm_complete; prep.current_speed = prep.exit_speed; } dt += time_var; // Add computed ramp time to total segment time. @@ -805,19 +844,19 @@ void st_prep_buffer() // through distance calculations until minimum_mm or mm_complete. dt_max += DT_SEGMENT; time_var = dt_max - dt; - } else { + } else { break; // **Complete** Exit loop. Segment execution time maxed. } } } while (mm_remaining > prep.mm_complete); // **Complete** Exit loop. Profile complete. - + /* ----------------------------------------------------------------------------------- Compute segment step rate, steps to execute, and apply necessary rate corrections. - NOTE: Steps are computed by direct scalar conversion of the millimeter distance + NOTE: Steps are computed by direct scalar conversion of the millimeter distance remaining in the block, rather than incrementally tallying the steps executed per - segment. This helps in removing floating point round-off issues of several additions. - However, since floats have only 7.2 significant digits, long moves with extremely + segment. This helps in removing floating point round-off issues of several additions. + However, since floats have only 7.2 significant digits, long moves with extremely high step counts can exceed the precision of floats, which can lead to lost steps. Fortunately, this scenario is highly unlikely and unrealistic in CNC machines supported by Grbl (i.e. exceeding 10 meters axis travel at 200 step/mm). @@ -826,48 +865,48 @@ void st_prep_buffer() float n_steps_remaining = ceil(step_dist_remaining); // Round-up current steps remaining float last_n_steps_remaining = ceil(prep.steps_remaining); // Round-up last steps remaining prep_segment->n_step = last_n_steps_remaining-n_steps_remaining; // Compute number of steps to execute. - + // Bail if we are at the end of a feed hold and don't have a step to execute. if (prep_segment->n_step == 0) { if (sys.step_control & STEP_CONTROL_EXECUTE_HOLD) { - // Less than one step to decelerate to zero speed, but already very close. AMASS + // Less than one step to decelerate to zero speed, but already very close. AMASS // requires full steps to execute. So, just bail. bit_true(sys.step_control,STEP_CONTROL_END_MOTION); #ifdef PARKING_ENABLE - if (!(prep.recalculate_flag & PREP_FLAG_PARKING)) { prep.recalculate_flag |= PREP_FLAG_HOLD_PARTIAL_BLOCK; } + if (!(prep.recalculate_flag & PREP_FLAG_PARKING)) { prep.recalculate_flag |= PREP_FLAG_HOLD_PARTIAL_BLOCK; } #endif return; // Segment not generated, but current step data still retained. } } // Compute segment step rate. Since steps are integers and mm distances traveled are not, - // the end of every segment can have a partial step of varying magnitudes that are not + // the end of every segment can have a partial step of varying magnitudes that are not // executed, because the stepper ISR requires whole steps due to the AMASS algorithm. To // compensate, we track the time to execute the previous segment's partial step and simply // apply it with the partial step distance to the current segment, so that it minutely - // adjusts the whole segment rate to keep step output exact. These rate adjustments are + // adjusts the whole segment rate to keep step output exact. These rate adjustments are // typically very small and do not adversely effect performance, but ensures that Grbl // outputs the exact acceleration and velocity profiles as computed by the planner. dt += prep.dt_remainder; // Apply previous segment partial step execute time float inv_rate = dt/(last_n_steps_remaining - step_dist_remaining); // Compute adjusted step rate inverse // Compute CPU cycles per step for the prepped segment. - uint32_t cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step) + uint32_t cycles = ceil( (TICKS_PER_MICROSECOND*1000000*60)*inv_rate ); // (cycles/step) - #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING + #ifdef ADAPTIVE_MULTI_AXIS_STEP_SMOOTHING // Compute step timing and multi-axis smoothing level. // NOTE: AMASS overdrives the timer with each level, so only one prescalar is required. if (cycles < AMASS_LEVEL1) { prep_segment->amass_level = 0; } else { if (cycles < AMASS_LEVEL2) { prep_segment->amass_level = 1; } else if (cycles < AMASS_LEVEL3) { prep_segment->amass_level = 2; } - else { prep_segment->amass_level = 3; } - cycles >>= prep_segment->amass_level; + else { prep_segment->amass_level = 3; } + cycles >>= prep_segment->amass_level; prep_segment->n_step <<= prep_segment->amass_level; } if (cycles < (1UL << 16)) { prep_segment->cycles_per_tick = cycles; } // < 65536 (4.1ms @ 16MHz) else { prep_segment->cycles_per_tick = 0xffff; } // Just set the slowest speed possible. - #else + #else // Compute step timing and timer prescalar for normal step generation. if (cycles < (1UL << 16)) { // < 65536 (4.1ms @ 16MHz) prep_segment->prescaler = 1; // prescaler: 0 @@ -875,7 +914,7 @@ void st_prep_buffer() } else if (cycles < (1UL << 19)) { // < 524288 (32.8ms@16MHz) prep_segment->prescaler = 2; // prescaler: 8 prep_segment->cycles_per_tick = cycles >> 3; - } else { + } else { prep_segment->prescaler = 3; // prescaler: 64 if (cycles < (1UL << 22)) { // < 4194304 (262ms@16MHz) prep_segment->cycles_per_tick = cycles >> 6; @@ -890,16 +929,16 @@ void st_prep_buffer() if ( ++segment_next_head == SEGMENT_BUFFER_SIZE ) { segment_next_head = 0; } // Update the appropriate planner and segment data. - pl_block->millimeters = mm_remaining; + pl_block->millimeters = mm_remaining; prep.steps_remaining = n_steps_remaining; prep.dt_remainder = (n_steps_remaining - step_dist_remaining)*inv_rate; - + // Check for exit conditions and flag to load next planner block. - if (mm_remaining == prep.mm_complete) { + if (mm_remaining == prep.mm_complete) { // End of planner block or forced-termination. No more distance to be executed. if (mm_remaining > 0.0) { // At end of forced-termination. // Reset prep parameters for resuming and then bail. Allow the stepper ISR to complete - // the segment queue, where realtime protocol will set new state upon receiving the + // the segment queue, where realtime protocol will set new state upon receiving the // cycle stop flag from the ISR. Prep_segment is blocked until then. bit_true(sys.step_control,STEP_CONTROL_END_MOTION); #ifdef PARKING_ENABLE @@ -908,31 +947,27 @@ void st_prep_buffer() return; // Bail! } else { // End of planner block // The planner block is complete. All steps are set to be executed in the segment buffer. - #ifdef PARKING_ENABLE - if (sys.step_control & STEP_CONTROL_EXECUTE_PARK) { - bit_true(sys.step_control,STEP_CONTROL_END_MOTION); - return; - } - #endif + if (sys.step_control & STEP_CONTROL_EXECUTE_SYS_MOTION) { + bit_true(sys.step_control,STEP_CONTROL_END_MOTION); + return; + } pl_block = NULL; // Set pointer to indicate check and load next planner block. plan_discard_current_block(); } } - } -} + } +} // Called by realtime status reporting to fetch the current speed being executed. This value // however is not exactly the current speed, but the speed computed in the last step segment // in the segment buffer. It will always be behind by up to the number of segment blocks (-1) -// divided by the ACCELERATION TICKS PER SECOND in seconds. -#ifdef REPORT_REALTIME_RATE - float st_get_realtime_rate() - { - if (sys.state & (STATE_CYCLE | STATE_HOMING | STATE_HOLD | STATE_MOTION_CANCEL | STATE_SAFETY_DOOR)){ - return prep.current_speed; - } - return 0.0f; - } -#endif +// divided by the ACCELERATION TICKS PER SECOND in seconds. +float st_get_realtime_rate() +{ + if (sys.state & (STATE_CYCLE | STATE_HOMING | STATE_HOLD | STATE_JOG | STATE_SAFETY_DOOR)){ + return prep.current_speed; + } + return 0.0f; +} diff --git a/grbl/stepper.h b/grbl/stepper.h index 1c88680..41871a6 100644 --- a/grbl/stepper.h +++ b/grbl/stepper.h @@ -2,7 +2,7 @@ stepper.h - stepper motor driver: executes motion plans of planner.c using the stepper motors Part of Grbl - Copyright (c) 2011-2015 Sungeun K. Jeon + Copyright (c) 2011-2016 Sungeun K. Jeon for Gnea Research LLC Copyright (c) 2009-2011 Simen Svale Skogsrud Grbl is free software: you can redistribute it and/or modify @@ -20,7 +20,7 @@ */ #ifndef stepper_h -#define stepper_h +#define stepper_h #ifndef SEGMENT_BUFFER_SIZE #define SEGMENT_BUFFER_SIZE 6 @@ -38,7 +38,7 @@ void st_go_idle(); // Generate the step and direction port invert masks. void st_generate_step_dir_invert_masks(); -// Reset the stepper subsystem variables +// Reset the stepper subsystem variables void st_reset(); // Changes the run state of the step segment buffer to execute the special parking motion. @@ -46,7 +46,7 @@ void st_parking_setup_buffer(); // Restores the step segment buffer to the normal run state after a parking motion. void st_parking_restore_buffer(); - + // Reloads step segment buffer. Called continuously by realtime execution system. void st_prep_buffer(); @@ -54,8 +54,6 @@ void st_prep_buffer(); void st_update_plan_block_parameters(); // Called by realtime status reporting if realtime rate reporting is enabled in config.h. -#ifdef REPORT_REALTIME_RATE float st_get_realtime_rate(); -#endif #endif diff --git a/grbl/system.c b/grbl/system.c index 2054230..8903334 100644 --- a/grbl/system.c +++ b/grbl/system.c @@ -2,7 +2,7 @@ system.c - Handles system level commands and real-time processes Part of Grbl - Copyright (c) 2014-2015 Sungeun K. Jeon + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ void system_init() } -// Returns control pin state as a uint8 bitfield. Each bit indicates the input pin state, where +// Returns control pin state as a uint8 bitfield. Each bit indicates the input pin state, where // triggered is 1 and not triggered is 0. Invert mask is applied. Bitfield organization is // defined by the CONTROL_PIN_INDEX in the header file. uint8_t system_control_get_state() @@ -57,25 +57,25 @@ uint8_t system_control_get_state() // Pin change interrupt for pin-out commands, i.e. cycle start, feed hold, and reset. Sets -// only the realtime command execute variable to have the main program execute these when +// only the realtime command execute variable to have the main program execute these when // its ready. This works exactly like the character-based realtime commands when picked off // directly from the incoming serial data stream. -ISR(CONTROL_INT_vect) +ISR(CONTROL_INT_vect) { uint8_t pin = system_control_get_state(); - if (pin) { + if (pin) { if (bit_istrue(pin,CONTROL_PIN_INDEX_RESET)) { mc_reset(); } else if (bit_istrue(pin,CONTROL_PIN_INDEX_CYCLE_START)) { bit_true(sys_rt_exec_state, EXEC_CYCLE_START); #ifndef ENABLE_SAFETY_DOOR_INPUT_PIN } else if (bit_istrue(pin,CONTROL_PIN_INDEX_FEED_HOLD)) { - bit_true(sys_rt_exec_state, EXEC_FEED_HOLD); + bit_true(sys_rt_exec_state, EXEC_FEED_HOLD); #else } else if (bit_istrue(pin,CONTROL_PIN_INDEX_SAFETY_DOOR)) { bit_true(sys_rt_exec_state, EXEC_SAFETY_DOOR); #endif - } + } } } @@ -92,7 +92,7 @@ uint8_t system_check_safety_door_ajar() // Executes user startup script, if stored. -void system_execute_startup(char *line) +void system_execute_startup(char *line) { uint8_t n; for (n=0; n < N_STARTUP_LINE; n++) { @@ -103,29 +103,35 @@ void system_execute_startup(char *line) printString(line); // Echo startup line to indicate execution. report_status_message(gc_execute_line(line)); } - } - } + } + } } // Directs and executes one line of formatted input from protocol_process. While mostly -// incoming streaming g-code blocks, this also executes Grbl internal commands, such as +// incoming streaming g-code blocks, this also executes Grbl internal commands, such as // settings, initiating the homing cycle, and toggling switch states. This differs from -// the realtime command module by being susceptible to when Grbl is ready to execute the +// the realtime command module by being susceptible to when Grbl is ready to execute the // next line during a cycle, so for switches like block delete, the switch only effects -// the lines that are processed afterward, not necessarily real-time during a cycle, +// the lines that are processed afterward, not necessarily real-time during a cycle, // since there are motions already stored in the buffer. However, this 'lag' should not // be an issue, since these commands are not typically used during a cycle. -uint8_t system_execute_line(char *line) -{ - uint8_t char_counter = 1; +uint8_t system_execute_line(char *line) +{ + uint8_t char_counter = 1; uint8_t helper_var = 0; // Helper variable float parameter, value; switch( line[char_counter] ) { case 0 : report_grbl_help(); break; + case 'J' : // Jogging + // Execute only if in IDLE or JOG states. + if (sys.state != STATE_IDLE && sys.state != STATE_JOG) { return(STATUS_IDLE_ERROR); } + if(line[2] != '=') { return(STATUS_INVALID_STATEMENT); } + return(gc_execute_line(line)); // NOTE: $J= is ignored inside g-code parser and used to detect jog motions. + break; case '$': case 'G': case 'C': case 'X': - if ( line[(char_counter+1)] != 0 ) { return(STATUS_INVALID_STATEMENT); } - switch( line[char_counter] ) { + if ( line[2] != 0 ) { return(STATUS_INVALID_STATEMENT); } + switch( line[1] ) { case '$' : // Prints Grbl settings if ( sys.state & (STATE_CYCLE | STATE_HOLD) ) { return(STATUS_IDLE_ERROR); } // Block during cycle. Takes too long to print. else { report_grbl_settings(); } @@ -133,86 +139,82 @@ uint8_t system_execute_line(char *line) case 'G' : // Prints gcode parser state // TODO: Move this to realtime commands for GUIs to request this data during suspend-state. report_gcode_modes(); - break; + break; case 'C' : // Set check g-code mode [IDLE/CHECK] // Perform reset when toggling off. Check g-code mode should only work if Grbl // is idle and ready, regardless of alarm locks. This is mainly to keep things // simple and consistent. - if ( sys.state == STATE_CHECK_MODE ) { - mc_reset(); + if ( sys.state == STATE_CHECK_MODE ) { + mc_reset(); report_feedback_message(MESSAGE_DISABLED); } else { if (sys.state) { return(STATUS_IDLE_ERROR); } // Requires no alarm mode. sys.state = STATE_CHECK_MODE; report_feedback_message(MESSAGE_ENABLED); } - break; + break; case 'X' : // Disable alarm lock [ALARM] - if (sys.state == STATE_ALARM) { + if (sys.state == STATE_ALARM) { // Block if safety door is ajar. if (system_check_safety_door_ajar()) { return(STATUS_CHECK_DOOR); } report_feedback_message(MESSAGE_ALARM_UNLOCK); sys.state = STATE_IDLE; // Don't run startup script. Prevents stored moves in startup from causing accidents. } // Otherwise, no effect. - break; - // case 'J' : break; // Jogging methods - // TODO: Here jogging can be placed for execution as a seperate subprogram. It does not need to be - // susceptible to other realtime commands except for e-stop. The jogging function is intended to - // be a basic toggle on/off with controlled acceleration and deceleration to prevent skipped - // steps. The user would supply the desired feedrate, axis to move, and direction. Toggle on would - // start motion and toggle off would initiate a deceleration to stop. One could 'feather' the - // motion by repeatedly toggling to slow the motion to the desired location. Location data would - // need to be updated real-time and supplied to the user through status queries. - // More controlled exact motions can be taken care of by inputting G0 or G1 commands, which are - // handled by the planner. It would be possible for the jog subprogram to insert blocks into the - // block buffer without having the planner plan them. It would need to manage de/ac-celerations - // on its own carefully. This approach could be effective and possibly size/memory efficient. + break; } break; - default : + default : // Block any system command that requires the state as IDLE/ALARM. (i.e. EEPROM, homing) if ( !(sys.state == STATE_IDLE || sys.state == STATE_ALARM) ) { return(STATUS_IDLE_ERROR); } - switch( line[char_counter] ) { + switch( line[1] ) { case '#' : // Print Grbl NGC parameters - if ( line[++char_counter] != 0 ) { return(STATUS_INVALID_STATEMENT); } + if ( line[2] != 0 ) { return(STATUS_INVALID_STATEMENT); } else { report_ngc_parameters(); } - break; + break; case 'H' : // Perform homing cycle [IDLE/ALARM] - if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { + if (bit_istrue(settings.flags,BITFLAG_HOMING_ENABLE)) { // Block if safety door is ajar. if (system_check_safety_door_ajar()) { return(STATUS_CHECK_DOOR); } sys.state = STATE_HOMING; // Set system state variable - mc_homing_cycle(); + mc_homing_cycle(); if (!sys.abort) { // Execute startup scripts after successful homing. sys.state = STATE_IDLE; // Set to IDLE when complete. st_go_idle(); // Set steppers to the settings idle state before returning. - system_execute_startup(line); + system_execute_startup(line); } } else { return(STATUS_SETTING_DISABLED); } break; case 'I' : // Print or store build info. [IDLE/ALARM] - if ( line[++char_counter] == 0 ) { + if ( line[++char_counter] == 0 ) { settings_read_build_info(line); report_build_info(line); - } else { // Store startup line [IDLE/ALARM] - if(line[char_counter++] != '=') { return(STATUS_INVALID_STATEMENT); } - helper_var = char_counter; // Set helper variable as counter to start of user info line. - do { - line[char_counter-helper_var] = line[char_counter]; - } while (line[char_counter++] != 0); - settings_store_build_info(line); + #ifdef ENABLE_BUILD_INFO_WRITE_COMMAND + } else { // Store startup line [IDLE/ALARM] + if(line[char_counter++] != '=') { return(STATUS_INVALID_STATEMENT); } + helper_var = char_counter; // Set helper variable as counter to start of user info line. + do { + line[char_counter-helper_var] = line[char_counter]; + } while (line[char_counter++] != 0); + settings_store_build_info(line); + #endif } - break; + break; case 'R' : // Restore defaults [IDLE/ALARM] - if (line[++char_counter] != 'S') { return(STATUS_INVALID_STATEMENT); } - if (line[++char_counter] != 'T') { return(STATUS_INVALID_STATEMENT); } - if (line[++char_counter] != '=') { return(STATUS_INVALID_STATEMENT); } - if (line[char_counter+2] != 0) { return(STATUS_INVALID_STATEMENT); } - switch (line[++char_counter]) { - case '$': settings_restore(SETTINGS_RESTORE_DEFAULTS); break; - case '#': settings_restore(SETTINGS_RESTORE_PARAMETERS); break; - case '*': settings_restore(SETTINGS_RESTORE_ALL); break; + if (line[2] != 'S') { return(STATUS_INVALID_STATEMENT); } + if (line[3] != 'T') { return(STATUS_INVALID_STATEMENT); } + if (line[4] != '=') { return(STATUS_INVALID_STATEMENT); } + if (line[6] != 0) { return(STATUS_INVALID_STATEMENT); } + switch (line[5]) { + #ifdef ENABLE_RESTORE_EEPROM_DEFAULT_SETTINGS + case '$': settings_restore(SETTINGS_RESTORE_DEFAULTS); break; + #endif + #ifdef ENABLE_RESTORE_EEPROM_CLEAR_PARAMETERS + case '#': settings_restore(SETTINGS_RESTORE_PARAMETERS); break; + #endif + #ifdef ENABLE_RESTORE_EEPROM_WIPE_ALL + case '*': settings_restore(SETTINGS_RESTORE_ALL); break; + #endif default: return(STATUS_INVALID_STATEMENT); } report_feedback_message(MESSAGE_RESTORE_DEFAULTS); @@ -230,7 +232,7 @@ uint8_t system_execute_line(char *line) break; } else { // Store startup line [IDLE Only] Prevents motion during ALARM. if (sys.state != STATE_IDLE) { return(STATUS_IDLE_ERROR); } // Store only when idle. - helper_var = true; // Set helper_var to flag storing method. + helper_var = true; // Set helper_var to flag storing method. // No break. Continues into default: to read remaining command characters. } default : // Storing setting methods [IDLE/ALARM] @@ -245,7 +247,7 @@ uint8_t system_execute_line(char *line) // Execute gcode block to ensure block is valid. helper_var = gc_execute_line(line); // Set helper_var to returned status code. if (helper_var) { return(helper_var); } - else { + else { helper_var = trunc(parameter); // Set helper_var to int value of parameter settings_store_startup_line(helper_var,line); } @@ -254,12 +256,22 @@ uint8_t system_execute_line(char *line) if((line[char_counter] != 0) || (parameter > 255)) { return(STATUS_INVALID_STATEMENT); } return(settings_store_global_setting((uint8_t)parameter, value)); } - } + } } return(STATUS_OK); // If '$' command makes it to here, then everything's ok. } + +void system_flag_wco_change() +{ + #ifdef FORCE_BUFFER_SYNC_DURING_WCO_CHANGE + protocol_buffer_synchronize(); + #endif + sys.report_wco_counter = REPORT_WCO_REFRESH_BUSY_COUNT; +} + + // Returns machine position of axis 'idx'. Must be sent a 'step' array. // NOTE: If motor steps and machine position are not in the same coordinate frame, this function // serves as a central place to compute the transformation. @@ -267,10 +279,10 @@ float system_convert_axis_steps_to_mpos(int32_t *steps, uint8_t idx) { float pos; #ifdef COREXY - if (idx==A_MOTOR) { - pos = 0.5*((steps[A_MOTOR] + steps[B_MOTOR])/settings.steps_per_mm[idx]); - } else if (idx==B_MOTOR) { - pos = 0.5*((steps[A_MOTOR] - steps[B_MOTOR])/settings.steps_per_mm[idx]); + if (idx==X_AXIS) { + pos = (float)system_convert_corexy_to_x_axis_steps(steps) / settings.steps_per_mm[idx]; + } else if (idx==Y_AXIS) { + pos = (float)system_convert_corexy_to_y_axis_steps(steps) / settings.steps_per_mm[idx]; } else { pos = steps[idx]/settings.steps_per_mm[idx]; } @@ -291,31 +303,94 @@ void system_convert_array_steps_to_mpos(float *position, int32_t *steps) } +// CoreXY calculation only. Returns x or y-axis "steps" based on CoreXY motor steps. +#ifdef COREXY + int32_t system_convert_corexy_to_x_axis_steps(int32_t *steps) + { + return( (steps[A_MOTOR] + steps[B_MOTOR])/2 ); + } + int32_t system_convert_corexy_to_y_axis_steps(int32_t *steps) + { + return( (steps[A_MOTOR] - steps[B_MOTOR])/2 ); + } +#endif + + +// Checks and reports if target array exceeds machine travel limits. +uint8_t system_check_travel_limits(float *target) +{ + uint8_t idx; + for (idx=0; idx -settings.max_travel[idx]) { return(true); } + } else { + if (target[idx] > 0 || target[idx] < settings.max_travel[idx]) { return(true); } + } + #else + // NOTE: max_travel is stored as negative + if (target[idx] > 0 || target[idx] < settings.max_travel[idx]) { return(true); } + #endif + } + return(false); +} + + // Special handlers for setting and clearing Grbl's real-time execution flags. void system_set_exec_state_flag(uint8_t mask) { - uint8_t sreg = SREG; - cli(); + uint8_t sreg = SREG; + cli(); sys_rt_exec_state |= (mask); SREG = sreg; } void system_clear_exec_state_flag(uint8_t mask) { - uint8_t sreg = SREG; - cli(); + uint8_t sreg = SREG; + cli(); sys_rt_exec_state &= ~(mask); SREG = sreg; } -void system_set_exec_alarm_flag(uint8_t mask) { - uint8_t sreg = SREG; - cli(); - sys_rt_exec_alarm |= (mask); +void system_set_exec_alarm(uint8_t code) { + uint8_t sreg = SREG; + cli(); + sys_rt_exec_alarm = code; SREG = sreg; } void system_clear_exec_alarm_flag(uint8_t mask) { - uint8_t sreg = SREG; - cli(); + uint8_t sreg = SREG; + cli(); sys_rt_exec_alarm &= ~(mask); SREG = sreg; } + +void system_set_exec_motion_override_flag(uint8_t mask) { + uint8_t sreg = SREG; + cli(); + sys_rt_exec_motion_override |= (mask); + SREG = sreg; +} + +void system_set_exec_accessory_override_flag(uint8_t mask) { + uint8_t sreg = SREG; + cli(); + sys_rt_exec_accessory_override |= (mask); + SREG = sreg; +} + +void system_clear_exec_motion_overrides() { + uint8_t sreg = SREG; + cli(); + sys_rt_exec_motion_override = 0; + SREG = sreg; +} + +void system_clear_exec_accessory_overrides() { + uint8_t sreg = SREG; + cli(); + sys_rt_exec_accessory_override = 0; + SREG = sreg; +} diff --git a/grbl/system.h b/grbl/system.h index 3b3d8cf..90acc0f 100644 --- a/grbl/system.h +++ b/grbl/system.h @@ -2,7 +2,7 @@ system.h - Header for system level commands and real-time processes Part of Grbl - Copyright (c) 2014-2015 Sungeun K. Jeon + Copyright (c) 2014-2016 Sungeun K. Jeon for Gnea Research LLC Grbl is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,10 +23,10 @@ #include "grbl.h" -// Define system executor bit map. Used internally by realtime protocol as realtime command flags, +// Define system executor bit map. Used internally by realtime protocol as realtime command flags, // which notifies the main program to execute the specified realtime command asynchronously. // NOTE: The system executor uses an unsigned 8-bit volatile variable (8 flag limit.) The default -// flags are always false, so the realtime protocol only needs to check for a non-zero value to +// flags are always false, so the realtime protocol only needs to check for a non-zero value to // know when there is a realtime command to execute. #define EXEC_STATUS_REPORT bit(0) // bitmask 00000001 #define EXEC_CYCLE_START bit(1) // bitmask 00000010 @@ -36,16 +36,37 @@ #define EXEC_SAFETY_DOOR bit(5) // bitmask 00100000 #define EXEC_MOTION_CANCEL bit(6) // bitmask 01000000 -// Alarm executor bit map. -// NOTE: EXEC_CRITICAL_EVENT is an optional flag that must be set with an alarm flag. When enabled, -// this halts Grbl into an infinite loop until the user aknowledges the problem and issues a soft- -// reset command. For example, a hard limit event needs this type of halt and aknowledgement. -#define EXEC_CRITICAL_EVENT bit(0) // bitmask 00000001 (SPECIAL FLAG. See NOTE:) -#define EXEC_ALARM_HARD_LIMIT bit(1) // bitmask 00000010 -#define EXEC_ALARM_SOFT_LIMIT bit(2) // bitmask 00000100 -#define EXEC_ALARM_ABORT_CYCLE bit(3) // bitmask 00001000 -#define EXEC_ALARM_PROBE_FAIL bit(4) // bitmask 00010000 -#define EXEC_ALARM_HOMING_FAIL bit(5) // bitmask 00100000 +// Alarm executor codes. Valid values (1-255). Zero is reserved. +#define EXEC_ALARM_HARD_LIMIT 1 +#define EXEC_ALARM_SOFT_LIMIT 2 +#define EXEC_ALARM_ABORT_CYCLE 3 +#define EXEC_ALARM_PROBE_FAIL_INITIAL 4 +#define EXEC_ALARM_PROBE_FAIL_CONTACT 5 +#define EXEC_ALARM_HOMING_FAIL_RESET 6 +#define EXEC_ALARM_HOMING_FAIL_DOOR 7 +#define EXEC_ALARM_HOMING_FAIL_PULLOFF 8 +#define EXEC_ALARM_HOMING_FAIL_APPROACH 9 + +// Override bit maps. Realtime bitflags to control feed, rapid, spindle, and coolant overrides. +// Spindle/coolant and feed/rapids are separated into two controlling flag variables. +#define EXEC_FEED_OVR_RESET bit(0) +#define EXEC_FEED_OVR_COARSE_PLUS bit(1) +#define EXEC_FEED_OVR_COARSE_MINUS bit(2) +#define EXEC_FEED_OVR_FINE_PLUS bit(3) +#define EXEC_FEED_OVR_FINE_MINUS bit(4) +#define EXEC_RAPID_OVR_RESET bit(5) +#define EXEC_RAPID_OVR_MEDIUM bit(6) +#define EXEC_RAPID_OVR_LOW bit(7) +// #define EXEC_RAPID_OVR_EXTRA_LOW bit(*) // *NOT SUPPORTED* + +#define EXEC_SPINDLE_OVR_RESET bit(0) +#define EXEC_SPINDLE_OVR_COARSE_PLUS bit(1) +#define EXEC_SPINDLE_OVR_COARSE_MINUS bit(2) +#define EXEC_SPINDLE_OVR_FINE_PLUS bit(3) +#define EXEC_SPINDLE_OVR_FINE_MINUS bit(4) +#define EXEC_SPINDLE_OVR_STOP bit(5) +#define EXEC_COOLANT_FLOOD_OVR_TOGGLE bit(6) +#define EXEC_COOLANT_MIST_OVR_TOGGLE bit(7) // Define system state bit map. The state variable primarily tracks the individual functions // of Grbl to manage each without overlapping. It is also used as a messaging flag for @@ -56,8 +77,9 @@ #define STATE_HOMING bit(2) // Performing homing cycle #define STATE_CYCLE bit(3) // Cycle is running or motions are being executed. #define STATE_HOLD bit(4) // Active feed hold -#define STATE_SAFETY_DOOR bit(5) // Safety door is ajar. Feed holds and de-energizes system. -#define STATE_MOTION_CANCEL bit(6) // Motion cancel by feed hold and return to idle. +#define STATE_JOG bit(5) // Jogging mode. +#define STATE_SAFETY_DOOR bit(6) // Safety door is ajar. Feed holds and de-energizes system. +// #define STATE_SLEEP bit(7) // Sleep state. [Grbl-Mega Only] // Define system suspend flags. Used in various ways to manage suspend states and procedures. #define SUSPEND_DISABLE 0 // Must be zero. @@ -66,15 +88,15 @@ #define SUSPEND_RETRACT_COMPLETE bit(2) // (Safety door only) Indicates retraction and de-energizing is complete. #define SUSPEND_INITIATE_RESTORE bit(3) // (Safety door only) Flag to initiate resume procedures from a cycle start. #define SUSPEND_RESTORE_COMPLETE bit(4) // (Safety door only) Indicates ready to resume normal operation. -#define SUSPEND_SAFETY_DOOR_AJAR bit(5) // Indicates suspend was initiated by a safety door state. +#define SUSPEND_SAFETY_DOOR_AJAR bit(5) // Tracks safety door state for resuming. #define SUSPEND_MOTION_CANCEL bit(6) // Indicates a canceled resume motion. Currently used by probing routine. +#define SUSPEND_JOG_CANCEL bit(7) // Indicates a jog cancel in process and to reset buffers when complete. // Define step segment generator state flags. -#define STEP_CONTROL_NORMAL_OP 0 -// #define STEP_CONTROL_RECOMPUTE_ACTIVE_BLOCK bit(0) -#define STEP_CONTROL_END_MOTION bit(1) -#define STEP_CONTROL_EXECUTE_HOLD bit(2) -#define STEP_CONTROL_EXECUTE_PARK bit(3) +#define STEP_CONTROL_NORMAL_OP 0 +#define STEP_CONTROL_END_MOTION bit(0) +#define STEP_CONTROL_EXECUTE_HOLD bit(1) +#define STEP_CONTROL_EXECUTE_SYS_MOTION bit(2) // Define control pin index for Grbl internal use. Pin maps may change, but these values don't. #ifdef ENABLE_SAFETY_DOOR_INPUT_PIN @@ -90,28 +112,49 @@ #define CONTROL_PIN_INDEX_CYCLE_START bit(2) #endif +// Define toggle override control states. +#define TOGGLE_OVR_STOP_ENABLED bit(0) +#define TOGGLE_OVR_STOP_INITIATE bit(1) +#define TOGGLE_OVR_STOP_RESTORE bit(2) +#define TOGGLE_OVR_STOP_RESTORE_CYCLE bit(3) +#define TOGGLE_OVR_FLOOD_COOLANT bit(4) +#define TOGGLE_OVR_MIST_COOLANT bit(5) +#define TOGGLE_OVR_STOP_ACTIVE_MASK (TOGGLE_OVR_STOP_ENABLED|TOGGLE_OVR_STOP_INITIATE|TOGGLE_OVR_STOP_RESTORE|TOGGLE_OVR_STOP_RESTORE_CYCLE) +// NOTE: Mask is used to determine if spindle stop is active or disabled. + // Define global system variables typedef struct { - uint8_t abort; // System abort flag. Forces exit back to main loop for reset. - uint8_t state; // Tracks the current state of Grbl. - uint8_t suspend; // System suspend bitflag variable that manages holds, cancels, and safety door. - uint8_t soft_limit; // Tracks soft limit errors for the state machine (Boolean) - uint8_t step_control; - - int32_t position[N_AXIS]; // Real-time machine (aka home) position vector in steps. - // NOTE: This may need to be a volatile variable, if problems arise. - - int32_t probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. - uint8_t probe_succeeded; // Tracks if last probing cycle was successful. - uint8_t homing_axis_lock; // Locks axes when limits engage. Used as an axis motion mask in the stepper ISR. + uint8_t abort; // System abort flag. Forces exit back to main loop for reset. + uint8_t state; // Tracks the current system state of Grbl. + uint8_t suspend; // System suspend bitflag variable that manages holds, cancels, and safety door. + uint8_t soft_limit; // Tracks soft limit errors for the state machine. (boolean) + uint8_t step_control; // Governs the step segment generator depending on system state. + uint8_t probe_succeeded; // Tracks if last probing cycle was successful. + uint8_t homing_axis_lock; // Locks axes when limits engage. Used as an axis motion mask in the stepper ISR. + uint8_t f_override; // Feed rate override value in percent + uint8_t r_override; // Rapids override value in percent + uint8_t spindle_speed_ovr; // Spindle speed value in percent + uint8_t toggle_ovr_mask; // Tracks toggle override states + uint8_t report_ovr_counter; // Tracks when to add override data to status reports. + uint8_t report_wco_counter; // Tracks when to add work coordinate offset data to status reports. } system_t; extern system_t sys; -volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. -volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. -volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. +// NOTE: These position variables may need to be declared as volatiles, if problems arise. +int32_t sys_position[N_AXIS]; // Real-time machine (aka home) position vector in steps. +int32_t sys_probe_position[N_AXIS]; // Last probe position in machine coordinates and steps. +volatile uint8_t sys_probe_state; // Probing state value. Used to coordinate the probing cycle with stepper ISR. +volatile uint8_t sys_rt_exec_state; // Global realtime executor bitflag variable for state management. See EXEC bitmasks. +volatile uint8_t sys_rt_exec_alarm; // Global realtime executor bitflag variable for setting various alarms. +volatile uint8_t sys_rt_exec_motion_override; // Global realtime executor bitflag variable for motion-based overrides. +volatile uint8_t sys_rt_exec_accessory_override; // Global realtime executor bitflag variable for spindle/coolant overrides. + +#ifdef DEBUG + #define EXEC_DEBUG_REPORT bit(0) + volatile uint8_t sys_rt_exec_debug; +#endif // Initialize the serial protocol void system_init(); @@ -128,17 +171,33 @@ uint8_t system_execute_line(char *line); // Execute the startup script lines stored in EEPROM upon initialization void system_execute_startup(char *line); + +void system_flag_wco_change(); + // Returns machine position of axis 'idx'. Must be sent a 'step' array. float system_convert_axis_steps_to_mpos(int32_t *steps, uint8_t idx); // Updates a machine 'position' array based on the 'step' array sent. void system_convert_array_steps_to_mpos(float *position, int32_t *steps); +// CoreXY calculation only. Returns x or y-axis "steps" based on CoreXY motor steps. +#ifdef COREXY + int32_t system_convert_corexy_to_x_axis_steps(int32_t *steps); + int32_t system_convert_corexy_to_y_axis_steps(int32_t *steps); +#endif + +// Checks and reports if target array exceeds machine travel limits. +uint8_t system_check_travel_limits(float *target); + // Special handlers for setting and clearing Grbl's real-time execution flags. void system_set_exec_state_flag(uint8_t mask); void system_clear_exec_state_flag(uint8_t mask); -void system_set_exec_alarm_flag(uint8_t mask); +void system_set_exec_alarm(uint8_t code); void system_clear_exec_alarm_flag(uint8_t mask); +void system_set_exec_motion_override_flag(uint8_t mask); +void system_set_exec_accessory_override_flag(uint8_t mask); +void system_clear_exec_motion_overrides(); +void system_clear_exec_accessory_overrides(); #endif