2017-09-08 05:33:16 +02:00
/**
* Marlin 3 D Printer Firmware
2019-06-28 06:57:50 +02:00
* Copyright ( c ) 2019 MarlinFirmware [ https : //github.com/MarlinFirmware/Marlin]
2017-09-08 05:33:16 +02:00
*
* Based on Sprinter and grbl .
2019-06-28 06:57:50 +02:00
* Copyright ( c ) 2011 Camiel Gubbels / Erik van der Zalm
2017-09-08 05:33:16 +02:00
*
* This program 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 .
*
* This program 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 this program . If not , see < http : //www.gnu.org/licenses/>.
*
*/
/**
* motion . cpp
*/
# include "motion.h"
2017-09-08 22:35:25 +02:00
# include "endstops.h"
# include "stepper.h"
# include "planner.h"
# include "temperature.h"
2017-09-08 05:33:16 +02:00
# include "../gcode/gcode.h"
2017-09-08 22:35:25 +02:00
# include "../inc/MarlinConfig.h"
2017-09-08 05:33:16 +02:00
# if IS_SCARA
# include "../libs/buzzer.h"
# include "../lcd/ultralcd.h"
# endif
2017-09-08 22:35:25 +02:00
# if HAS_BED_PROBE
# include "probe.h"
# endif
# if HAS_LEVELING
# include "../feature/bedlevel/bedlevel.h"
# endif
2019-03-17 11:57:25 +01:00
# if ENABLED(BLTOUCH)
# include "../feature/bltouch.h"
# endif
2019-07-17 10:12:39 +02:00
# if HAS_DISPLAY
2017-09-08 22:35:25 +02:00
# include "../lcd/ultralcd.h"
# endif
# if ENABLED(SENSORLESS_HOMING)
2017-12-15 22:03:14 +01:00
# include "../feature/tmc_util.h"
2017-09-08 05:33:16 +02:00
# endif
2018-01-29 20:40:04 +01:00
# if ENABLED(FWRETRACT)
# include "../feature/fwretract.h"
# endif
2019-04-07 01:04:34 +02:00
# if ENABLED(BABYSTEP_DISPLAY_TOTAL)
# include "../feature/babystep.h"
# endif
2019-03-14 08:25:42 +01:00
# define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
# include "../core/debug_out.h"
2017-09-08 05:33:16 +02:00
# define XYZ_CONSTS(type, array, CONFIG) const PROGMEM type array##_P[XYZ] = { X_##CONFIG, Y_##CONFIG, Z_##CONFIG }
XYZ_CONSTS ( float , base_min_pos , MIN_POS ) ;
XYZ_CONSTS ( float , base_max_pos , MAX_POS ) ;
XYZ_CONSTS ( float , base_home_pos , HOME_POS ) ;
XYZ_CONSTS ( float , max_length , MAX_LENGTH ) ;
XYZ_CONSTS ( float , home_bump_mm , HOME_BUMP_MM ) ;
XYZ_CONSTS ( signed char , home_dir , HOME_DIR ) ;
2018-10-31 23:07:52 +01:00
/**
* axis_homed
* Flags that each linear axis was homed .
* XYZ on cartesian , ABC on delta , ABZ on SCARA .
*
* axis_known_position
* Flags that the position is known in each linear axis . Set when homed .
* Cleared whenever a stepper powers off , potentially losing its position .
*/
uint8_t axis_homed , axis_known_position ; // = 0
2017-09-08 05:33:16 +02:00
// Relative Mode. Enable with G91, disable with G90.
2018-03-21 11:45:23 +01:00
bool relative_mode ; // = false;
2017-09-08 05:33:16 +02:00
/**
* Cartesian Current Position
2017-11-03 05:59:42 +01:00
* Used to track the native machine position as moves are queued .
2019-03-05 02:09:51 +01:00
* Used by ' line_to_current_position ' to do a move after changing it .
2018-09-17 04:24:15 +02:00
* Used by ' sync_plan_position ' to update ' planner . position ' .
2017-09-08 05:33:16 +02:00
*/
2018-12-03 01:06:20 +01:00
float current_position [ XYZE ] = { X_HOME_POS , Y_HOME_POS , Z_HOME_POS } ;
2017-09-08 05:33:16 +02:00
/**
* Cartesian Destination
2017-11-09 05:13:33 +01:00
* The destination for a move , filled in by G - code movement commands ,
* and expected by functions like ' prepare_move_to_destination ' .
2019-09-14 10:05:10 +02:00
* G - codes can set destination using ' get_destination_from_command '
2017-09-08 05:33:16 +02:00
*/
2018-12-03 01:06:20 +01:00
float destination [ XYZE ] ; // = { 0 }
2017-09-08 05:33:16 +02:00
// The active extruder (tool). Set with T<extruder> command.
2018-09-11 06:09:26 +02:00
# if EXTRUDERS > 1
uint8_t active_extruder ; // = 0
# endif
2017-09-08 05:33:16 +02:00
2017-09-08 22:35:25 +02:00
// Extruder offsets
2018-08-25 04:26:29 +02:00
# if HAS_HOTEND_OFFSET
2017-09-08 22:35:25 +02:00
float hotend_offset [ XYZ ] [ HOTENDS ] ; // Initialized by settings.load()
2019-03-09 05:13:24 +01:00
void reset_hotend_offsets ( ) {
constexpr float tmp [ XYZ ] [ HOTENDS ] = { HOTEND_OFFSET_X , HOTEND_OFFSET_Y , HOTEND_OFFSET_Z } ;
static_assert (
tmp [ X_AXIS ] [ 0 ] = = 0 & & tmp [ Y_AXIS ] [ 0 ] = = 0 & & tmp [ Z_AXIS ] [ 0 ] = = 0 ,
" Offsets for the first hotend must be 0.0. "
) ;
LOOP_XYZ ( i ) HOTEND_LOOP ( ) hotend_offset [ i ] [ e ] = tmp [ i ] [ e ] ;
# if ENABLED(DUAL_X_CARRIAGE)
2019-07-06 01:01:21 +02:00
hotend_offset [ X_AXIS ] [ 1 ] = _MAX ( X2_HOME_POS , X2_MAX_POS ) ;
2019-03-09 05:13:24 +01:00
# endif
}
2017-09-08 22:35:25 +02:00
# endif
2017-09-08 05:33:16 +02:00
// The feedrate for the current move, often used as the default if
// no other feedrate is specified. Overridden for special moves.
// Set by the last G0 through G5 command's "F" parameter.
// Functions that override this for custom moves *must always* restore it!
2018-07-01 22:20:28 +02:00
float feedrate_mm_s = MMM_TO_MMS ( 1500.0f ) ;
2017-09-08 05:33:16 +02:00
2017-09-08 22:35:25 +02:00
int16_t feedrate_percentage = 100 ;
// Homing feedrate is const progmem - compare to constexpr in the header
2019-01-24 02:25:57 +01:00
const float homing_feedrate_mm_s [ XYZ ] PROGMEM = {
2017-09-08 22:35:25 +02:00
# if ENABLED(DELTA)
MMM_TO_MMS ( HOMING_FEEDRATE_Z ) , MMM_TO_MMS ( HOMING_FEEDRATE_Z ) ,
# else
MMM_TO_MMS ( HOMING_FEEDRATE_XY ) , MMM_TO_MMS ( HOMING_FEEDRATE_XY ) ,
# endif
2019-01-24 02:25:57 +01:00
MMM_TO_MMS ( HOMING_FEEDRATE_Z )
2017-09-08 22:35:25 +02:00
} ;
// Cartesian conversion result goes here:
float cartes [ XYZ ] ;
# if IS_KINEMATIC
2019-03-09 05:13:24 +01:00
2017-09-08 22:35:25 +02:00
float delta [ ABC ] ;
2019-03-09 05:13:24 +01:00
# if HAS_SCARA_OFFSET
float scara_home_offset [ ABC ] ;
# endif
# if HAS_SOFTWARE_ENDSTOPS
2019-03-13 11:48:36 +01:00
float delta_max_radius , delta_max_radius_2 ;
2019-03-09 05:13:24 +01:00
# elif IS_SCARA
2019-03-13 11:48:36 +01:00
constexpr float delta_max_radius = SCARA_PRINTABLE_RADIUS ,
delta_max_radius_2 = sq ( SCARA_PRINTABLE_RADIUS ) ;
2019-03-09 05:13:24 +01:00
# else // DELTA
2019-03-13 11:48:36 +01:00
constexpr float delta_max_radius = DELTA_PRINTABLE_RADIUS ,
delta_max_radius_2 = sq ( DELTA_PRINTABLE_RADIUS ) ;
2019-03-09 05:13:24 +01:00
# endif
2018-11-03 09:56:33 +01:00
# endif
2017-09-08 22:35:25 +02:00
/**
* The workspace can be offset by some commands , or
* these offsets may be omitted to save on computation .
*/
2018-11-03 09:39:15 +01:00
# if HAS_POSITION_SHIFT
// The distance that XYZ has been offset by G92. Reset by G28.
float position_shift [ XYZ ] = { 0 } ;
# endif
# if HAS_HOME_OFFSET
// This offset is added to the configured home position.
// Set by M206, M428, or menu item. Saved to EEPROM.
float home_offset [ XYZ ] = { 0 } ;
# endif
# if HAS_HOME_OFFSET && HAS_POSITION_SHIFT
// The above two are combined to save on computes
float workspace_offset [ XYZ ] = { 0 } ;
2017-09-08 22:35:25 +02:00
# endif
2019-02-25 03:29:03 +01:00
# if HAS_ABL_NOT_UBL
2017-09-08 22:35:25 +02:00
float xy_probe_feedrate_mm_s = MMM_TO_MMS ( XY_PROBE_SPEED ) ;
# endif
/**
* Output the current position to serial
*/
void report_current_position ( ) {
2018-11-29 23:58:58 +01:00
SERIAL_ECHOPAIR ( " X: " , LOGICAL_X_POSITION ( current_position [ X_AXIS ] ) ) ;
SERIAL_ECHOPAIR ( " Y: " , LOGICAL_Y_POSITION ( current_position [ Y_AXIS ] ) ) ;
SERIAL_ECHOPAIR ( " Z: " , LOGICAL_Z_POSITION ( current_position [ Z_AXIS ] ) ) ;
SERIAL_ECHOPAIR ( " E: " , current_position [ E_AXIS ] ) ;
2017-09-08 22:35:25 +02:00
stepper . report_positions ( ) ;
# if IS_SCARA
scara_report_positions ( ) ;
# endif
}
2017-09-08 05:33:16 +02:00
/**
* sync_plan_position
*
* Set the planner / stepper positions directly from current_position with
* no kinematic translation . Used for homing axes and cartesian / core syncing .
*/
void sync_plan_position ( ) {
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " sync_plan_position " , current_position ) ;
2017-09-08 05:33:16 +02:00
planner . set_position_mm ( current_position [ X_AXIS ] , current_position [ Y_AXIS ] , current_position [ Z_AXIS ] , current_position [ E_AXIS ] ) ;
}
void sync_plan_position_e ( ) { planner . set_e_position_mm ( current_position [ E_AXIS ] ) ; }
2017-09-08 22:35:25 +02:00
/**
* Get the stepper positions in the cartes [ ] array .
* Forward kinematics are applied for DELTA and SCARA .
*
* The result is in the current coordinate space with
* leveling applied . The coordinates need to be run through
* unapply_leveling to obtain the " ideal " coordinates
* suitable for current_position , etc .
*/
void get_cartesian_from_steppers ( ) {
# if ENABLED(DELTA)
forward_kinematics_DELTA (
2018-05-12 16:59:11 +02:00
planner . get_axis_position_mm ( A_AXIS ) ,
planner . get_axis_position_mm ( B_AXIS ) ,
planner . get_axis_position_mm ( C_AXIS )
2017-09-08 22:35:25 +02:00
) ;
# else
2017-11-03 05:59:42 +01:00
# if IS_SCARA
forward_kinematics_SCARA (
2018-05-12 16:59:11 +02:00
planner . get_axis_position_degrees ( A_AXIS ) ,
planner . get_axis_position_degrees ( B_AXIS )
2017-11-03 05:59:42 +01:00
) ;
# else
2018-05-12 16:59:11 +02:00
cartes [ X_AXIS ] = planner . get_axis_position_mm ( X_AXIS ) ;
cartes [ Y_AXIS ] = planner . get_axis_position_mm ( Y_AXIS ) ;
2017-11-03 05:59:42 +01:00
# endif
2018-05-12 16:59:11 +02:00
cartes [ Z_AXIS ] = planner . get_axis_position_mm ( Z_AXIS ) ;
2017-09-08 22:35:25 +02:00
# endif
}
/**
* Set the current_position for an axis based on
* the stepper positions , removing any leveling that
* may have been applied .
2017-12-11 04:17:07 +01:00
*
* To prevent small shifts in axis position always call
2018-09-17 04:24:15 +02:00
* sync_plan_position after updating axes with this .
2017-12-11 04:17:07 +01:00
*
* To keep hosts in sync , always call report_current_position
* after updating the current_position .
2017-09-08 22:35:25 +02:00
*/
void set_current_from_steppers_for_axis ( const AxisEnum axis ) {
get_cartesian_from_steppers ( ) ;
2018-09-17 04:24:15 +02:00
# if HAS_POSITION_MODIFIERS
float pos [ XYZE ] = { cartes [ X_AXIS ] , cartes [ Y_AXIS ] , cartes [ Z_AXIS ] , current_position [ E_AXIS ] } ;
planner . unapply_modifiers ( pos
# if HAS_LEVELING
, true
# endif
) ;
const float ( & cartes ) [ XYZE ] = pos ;
2017-09-08 22:35:25 +02:00
# endif
if ( axis = = ALL_AXES )
COPY ( current_position , cartes ) ;
else
current_position [ axis ] = cartes [ axis ] ;
}
2017-09-08 05:33:16 +02:00
/**
* Move the planner to the current position from wherever it last moved
* ( or from wherever it has been told it is located ) .
*/
2019-02-25 21:28:01 +01:00
void line_to_current_position ( const float & fr_mm_s /*=feedrate_mm_s*/ ) {
planner . buffer_line ( current_position [ X_AXIS ] , current_position [ Y_AXIS ] , current_position [ Z_AXIS ] , current_position [ E_AXIS ] , fr_mm_s , active_extruder ) ;
2017-09-08 05:33:16 +02:00
}
/**
* Move the planner to the position stored in the destination array , which is
* used by G0 / G1 / G2 / G3 / G5 and many other functions to set a destination .
*/
2017-11-09 05:13:33 +01:00
void buffer_line_to_destination ( const float fr_mm_s ) {
2017-09-08 05:33:16 +02:00
planner . buffer_line ( destination [ X_AXIS ] , destination [ Y_AXIS ] , destination [ Z_AXIS ] , destination [ E_AXIS ] , fr_mm_s , active_extruder ) ;
}
# if IS_KINEMATIC
/**
* Calculate delta , start a line , and set current_position to destination
*/
2019-02-25 21:28:01 +01:00
void prepare_uninterpolated_move_to_destination ( const float & fr_mm_s /*=0.0*/ ) {
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " prepare_uninterpolated_move_to_destination " , destination ) ;
2017-09-08 05:33:16 +02:00
2017-12-09 09:11:31 +01:00
# if UBL_SEGMENTED
2017-09-08 05:33:16 +02:00
// ubl segmented line will do z-only moves in single segment
ubl . prepare_segmented_line_to ( destination , MMS_SCALED ( fr_mm_s ? fr_mm_s : feedrate_mm_s ) ) ;
# else
if ( current_position [ X_AXIS ] = = destination [ X_AXIS ]
& & current_position [ Y_AXIS ] = = destination [ Y_AXIS ]
& & current_position [ Z_AXIS ] = = destination [ Z_AXIS ]
& & current_position [ E_AXIS ] = = destination [ E_AXIS ]
) return ;
2018-09-17 04:24:15 +02:00
planner . buffer_line ( destination , MMS_SCALED ( fr_mm_s ? fr_mm_s : feedrate_mm_s ) , active_extruder ) ;
2017-09-08 05:33:16 +02:00
# endif
2017-10-21 18:42:26 +02:00
set_current_from_destination ( ) ;
2017-09-08 05:33:16 +02:00
}
# endif // IS_KINEMATIC
2017-09-08 22:35:25 +02:00
/**
2019-02-14 12:25:29 +01:00
* Plan a move to ( X , Y , Z ) and set the current_position
2017-09-08 22:35:25 +02:00
*/
2018-03-12 14:16:08 +01:00
void do_blocking_move_to ( const float rx , const float ry , const float rz , const float & fr_mm_s /*=0.0*/ ) {
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_XYZ ( " >>> do_blocking_move_to " , rx , ry , rz ) ;
2017-09-08 22:35:25 +02:00
2019-02-25 21:28:01 +01:00
const float z_feedrate = fr_mm_s ? fr_mm_s : homing_feedrate ( Z_AXIS ) ,
xy_feedrate = fr_mm_s ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S ;
2017-11-26 06:31:16 +01:00
2017-09-08 22:35:25 +02:00
# if ENABLED(DELTA)
2017-11-03 05:59:42 +01:00
if ( ! position_is_reachable ( rx , ry ) ) return ;
2017-09-08 22:35:25 +02:00
2019-02-25 21:28:01 +01:00
REMEMBER ( fr , feedrate_mm_s , xy_feedrate ) ;
2017-09-08 22:35:25 +02:00
2017-10-21 18:42:26 +02:00
set_destination_from_current ( ) ; // sync destination at the start
2017-09-08 22:35:25 +02:00
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " set_destination_from_current " , destination ) ;
2017-09-08 22:35:25 +02:00
// when in the danger zone
if ( current_position [ Z_AXIS ] > delta_clip_start_height ) {
2017-11-03 05:59:42 +01:00
if ( rz > delta_clip_start_height ) { // staying in the danger zone
destination [ X_AXIS ] = rx ; // move directly (uninterpolated)
destination [ Y_AXIS ] = ry ;
destination [ Z_AXIS ] = rz ;
2017-10-21 18:42:26 +02:00
prepare_uninterpolated_move_to_destination ( ) ; // set_current_from_destination()
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " danger zone move " , current_position ) ;
2017-09-08 22:35:25 +02:00
return ;
}
2017-11-26 06:31:16 +01:00
destination [ Z_AXIS ] = delta_clip_start_height ;
prepare_uninterpolated_move_to_destination ( ) ; // set_current_from_destination()
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " zone border move " , current_position ) ;
2017-09-08 22:35:25 +02:00
}
2017-11-03 05:59:42 +01:00
if ( rz > current_position [ Z_AXIS ] ) { // raising?
destination [ Z_AXIS ] = rz ;
2017-11-26 06:31:16 +01:00
prepare_uninterpolated_move_to_destination ( z_feedrate ) ; // set_current_from_destination()
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " z raise move " , current_position ) ;
2017-09-08 22:35:25 +02:00
}
2017-11-03 05:59:42 +01:00
destination [ X_AXIS ] = rx ;
destination [ Y_AXIS ] = ry ;
2017-10-21 18:42:26 +02:00
prepare_move_to_destination ( ) ; // set_current_from_destination()
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " xy move " , current_position ) ;
2017-09-08 22:35:25 +02:00
2017-11-03 05:59:42 +01:00
if ( rz < current_position [ Z_AXIS ] ) { // lowering?
destination [ Z_AXIS ] = rz ;
2017-11-26 06:31:16 +01:00
prepare_uninterpolated_move_to_destination ( z_feedrate ) ; // set_current_from_destination()
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " z lower move " , current_position ) ;
2017-09-08 22:35:25 +02:00
}
# elif IS_SCARA
2017-11-03 05:59:42 +01:00
if ( ! position_is_reachable ( rx , ry ) ) return ;
2017-09-08 22:35:25 +02:00
2017-10-21 18:42:26 +02:00
set_destination_from_current ( ) ;
2017-09-08 22:35:25 +02:00
// If Z needs to raise, do it before moving XY
2017-11-03 05:59:42 +01:00
if ( destination [ Z_AXIS ] < rz ) {
destination [ Z_AXIS ] = rz ;
2017-11-26 06:31:16 +01:00
prepare_uninterpolated_move_to_destination ( z_feedrate ) ;
2017-09-08 22:35:25 +02:00
}
2017-11-03 05:59:42 +01:00
destination [ X_AXIS ] = rx ;
destination [ Y_AXIS ] = ry ;
2019-02-25 21:28:01 +01:00
prepare_uninterpolated_move_to_destination ( xy_feedrate ) ;
2017-09-08 22:35:25 +02:00
// If Z needs to lower, do it after moving XY
2017-11-03 05:59:42 +01:00
if ( destination [ Z_AXIS ] > rz ) {
destination [ Z_AXIS ] = rz ;
2017-11-26 06:31:16 +01:00
prepare_uninterpolated_move_to_destination ( z_feedrate ) ;
2017-09-08 22:35:25 +02:00
}
# else
// If Z needs to raise, do it before moving XY
2017-11-03 05:59:42 +01:00
if ( current_position [ Z_AXIS ] < rz ) {
current_position [ Z_AXIS ] = rz ;
2019-02-25 21:28:01 +01:00
line_to_current_position ( z_feedrate ) ;
2017-09-08 22:35:25 +02:00
}
2017-11-03 05:59:42 +01:00
current_position [ X_AXIS ] = rx ;
current_position [ Y_AXIS ] = ry ;
2019-02-25 21:28:01 +01:00
line_to_current_position ( xy_feedrate ) ;
2017-09-08 22:35:25 +02:00
// If Z needs to lower, do it after moving XY
2017-11-03 05:59:42 +01:00
if ( current_position [ Z_AXIS ] > rz ) {
current_position [ Z_AXIS ] = rz ;
2019-02-25 21:28:01 +01:00
line_to_current_position ( z_feedrate ) ;
2017-09-08 22:35:25 +02:00
}
# endif
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " <<< do_blocking_move_to " ) ;
2018-05-04 03:51:10 +02:00
2018-05-12 08:38:02 +02:00
planner . synchronize ( ) ;
2017-09-08 22:35:25 +02:00
}
2017-11-03 05:59:42 +01:00
void do_blocking_move_to_x ( const float & rx , const float & fr_mm_s /*=0.0*/ ) {
do_blocking_move_to ( rx , current_position [ Y_AXIS ] , current_position [ Z_AXIS ] , fr_mm_s ) ;
2017-09-08 22:35:25 +02:00
}
2019-08-01 03:50:23 +02:00
void do_blocking_move_to_y ( const float & ry , const float & fr_mm_s /*=0.0*/ ) {
2019-09-05 01:09:12 +02:00
do_blocking_move_to ( current_position [ X_AXIS ] , ry , current_position [ Z_AXIS ] , fr_mm_s ) ;
2019-08-01 03:50:23 +02:00
}
2017-11-03 05:59:42 +01:00
void do_blocking_move_to_z ( const float & rz , const float & fr_mm_s /*=0.0*/ ) {
do_blocking_move_to ( current_position [ X_AXIS ] , current_position [ Y_AXIS ] , rz , fr_mm_s ) ;
2017-09-08 22:35:25 +02:00
}
2017-11-03 05:59:42 +01:00
void do_blocking_move_to_xy ( const float & rx , const float & ry , const float & fr_mm_s /*=0.0*/ ) {
do_blocking_move_to ( rx , ry , current_position [ Z_AXIS ] , fr_mm_s ) ;
2017-09-08 22:35:25 +02:00
}
//
2019-02-25 21:28:01 +01:00
// Prepare to do endstop or probe moves with custom feedrates.
// - Save / restore current feedrate and multiplier
2017-09-08 22:35:25 +02:00
//
2019-02-25 21:28:01 +01:00
static float saved_feedrate_mm_s ;
static int16_t saved_feedrate_percentage ;
2019-09-24 03:58:01 +02:00
void remember_feedrate_and_scaling ( ) {
2019-02-25 21:28:01 +01:00
saved_feedrate_mm_s = feedrate_mm_s ;
saved_feedrate_percentage = feedrate_percentage ;
2019-09-24 03:58:01 +02:00
}
void remember_feedrate_scaling_off ( ) {
remember_feedrate_and_scaling ( ) ;
2019-02-25 21:28:01 +01:00
feedrate_percentage = 100 ;
}
2019-09-24 03:58:01 +02:00
void restore_feedrate_and_scaling ( ) {
2019-02-25 21:28:01 +01:00
feedrate_mm_s = saved_feedrate_mm_s ;
feedrate_percentage = saved_feedrate_percentage ;
2017-09-08 22:35:25 +02:00
}
2017-09-08 05:33:16 +02:00
# if HAS_SOFTWARE_ENDSTOPS
bool soft_endstops_enabled = true ;
2018-11-03 09:56:33 +01:00
// Software Endstops are based on the configured limits.
2019-03-13 11:48:36 +01:00
axis_limits_t soft_endstop [ XYZ ] = { { X_MIN_BED , X_MAX_BED } , { Y_MIN_BED , Y_MAX_BED } , { Z_MIN_POS , Z_MAX_POS } } ;
2018-11-03 09:56:33 +01:00
/**
* Software endstops can be used to monitor the open end of
* an axis that has a hardware endstop on the other end . Or
* they can prevent axes from moving past endstops and grinding .
*
* To keep doing their job as the coordinate system changes ,
* the software endstop positions must be refreshed to remain
* at the same positions relative to the machine .
*/
2019-03-09 05:13:24 +01:00
void update_software_endstops ( const AxisEnum axis
# if HAS_HOTEND_OFFSET
, const uint8_t old_tool_index /*=0*/ , const uint8_t new_tool_index /*=0*/
# endif
) {
2018-11-03 09:56:33 +01:00
# if ENABLED(DUAL_X_CARRIAGE)
if ( axis = = X_AXIS ) {
// In Dual X mode hotend_offset[X] is T1's home position
2019-07-06 01:01:21 +02:00
const float dual_max_x = _MAX ( hotend_offset [ X_AXIS ] [ 1 ] , X2_MAX_POS ) ;
2018-11-03 09:56:33 +01:00
2019-03-09 05:13:24 +01:00
if ( new_tool_index ! = 0 ) {
2018-11-03 09:56:33 +01:00
// T1 can move from X2_MIN_POS to X2_MAX_POS or X2 home position (whichever is larger)
2019-03-13 11:48:36 +01:00
soft_endstop [ X_AXIS ] . min = X2_MIN_POS ;
soft_endstop [ X_AXIS ] . max = dual_max_x ;
2018-11-03 09:56:33 +01:00
}
else if ( dxc_is_duplicating ( ) ) {
// In Duplication Mode, T0 can move as far left as X1_MIN_POS
// but not so far to the right that T1 would move past the end
2019-03-13 11:48:36 +01:00
soft_endstop [ X_AXIS ] . min = X1_MIN_POS ;
2019-07-06 01:01:21 +02:00
soft_endstop [ X_AXIS ] . max = _MIN ( X1_MAX_POS , dual_max_x - duplicate_extruder_x_offset ) ;
2018-11-03 09:56:33 +01:00
}
else {
// In other modes, T0 can move from X1_MIN_POS to X1_MAX_POS
2019-03-13 11:48:36 +01:00
soft_endstop [ X_AXIS ] . min = X1_MIN_POS ;
soft_endstop [ X_AXIS ] . max = X1_MAX_POS ;
2018-11-03 09:56:33 +01:00
}
2019-03-13 11:48:36 +01:00
2018-11-03 09:56:33 +01:00
}
# elif ENABLED(DELTA)
2019-03-13 11:48:36 +01:00
soft_endstop [ axis ] . min = base_min_pos ( axis ) ;
soft_endstop [ axis ] . max = ( axis = = Z_AXIS ? delta_height
2018-11-03 09:56:33 +01:00
# if HAS_BED_PROBE
2019-09-25 06:35:49 +02:00
- probe_offset [ Z_AXIS ]
2018-11-03 09:56:33 +01:00
# endif
: base_max_pos ( axis ) ) ;
switch ( axis ) {
case X_AXIS :
case Y_AXIS :
// Get a minimum radius for clamping
2019-07-06 01:01:21 +02:00
delta_max_radius = _MIN ( ABS ( _MAX ( soft_endstop [ X_AXIS ] . min , soft_endstop [ Y_AXIS ] . min ) ) , soft_endstop [ X_AXIS ] . max , soft_endstop [ Y_AXIS ] . max ) ;
2019-03-13 11:48:36 +01:00
delta_max_radius_2 = sq ( delta_max_radius ) ;
2018-11-03 09:56:33 +01:00
break ;
case Z_AXIS :
2019-03-13 11:48:36 +01:00
delta_clip_start_height = soft_endstop [ axis ] . max - delta_safe_distance_from_top ( ) ;
2018-11-03 09:56:33 +01:00
default : break ;
}
2019-03-09 05:13:24 +01:00
# elif HAS_HOTEND_OFFSET
// Software endstops are relative to the tool 0 workspace, so
// the movement limits must be shifted by the tool offset to
// retain the same physical limit when other tools are selected.
if ( old_tool_index ! = new_tool_index ) {
const float offs = hotend_offset [ axis ] [ new_tool_index ] - hotend_offset [ axis ] [ old_tool_index ] ;
2019-03-13 11:48:36 +01:00
soft_endstop [ axis ] . min + = offs ;
soft_endstop [ axis ] . max + = offs ;
2019-03-09 05:13:24 +01:00
}
else {
const float offs = hotend_offset [ axis ] [ active_extruder ] ;
2019-03-13 11:48:36 +01:00
soft_endstop [ axis ] . min = base_min_pos ( axis ) + offs ;
soft_endstop [ axis ] . max = base_max_pos ( axis ) + offs ;
2019-03-09 05:13:24 +01:00
}
2018-11-03 09:56:33 +01:00
# else
2019-03-13 11:48:36 +01:00
soft_endstop [ axis ] . min = base_min_pos ( axis ) ;
soft_endstop [ axis ] . max = base_max_pos ( axis ) ;
2018-11-03 09:56:33 +01:00
# endif
2019-07-18 03:24:25 +02:00
if ( DEBUGGING ( LEVELING ) )
SERIAL_ECHOLNPAIR ( " Axis " , axis_codes [ axis ] , " min: " , soft_endstop [ axis ] . min , " max: " , soft_endstop [ axis ] . max ) ;
2019-03-13 11:48:36 +01:00
}
2018-11-03 09:56:33 +01:00
2019-03-13 11:48:36 +01:00
/**
* Constrain the given coordinates to the software endstops .
*
* For DELTA / SCARA the XY constraint is based on the smallest
* radius within the set software endstops .
*/
void apply_motion_limits ( float target [ XYZ ] ) {
2019-03-09 05:13:24 +01:00
2019-03-13 11:48:36 +01:00
if ( ! soft_endstops_enabled ) return ;
2019-03-09 05:13:24 +01:00
2019-03-13 11:48:36 +01:00
# if IS_KINEMATIC
2019-03-09 05:13:24 +01:00
2019-03-13 11:48:36 +01:00
# if HAS_HOTEND_OFFSET && ENABLED(DELTA)
// The effector center position will be the target minus the hotend offset.
const float offx = hotend_offset [ X_AXIS ] [ active_extruder ] , offy = hotend_offset [ Y_AXIS ] [ active_extruder ] ;
# else
// SCARA needs to consider the angle of the arm through the entire move, so for now use no tool offset.
constexpr float offx = 0 , offy = 0 ;
# endif
2019-03-09 05:13:24 +01:00
2019-03-13 11:48:36 +01:00
const float dist_2 = HYPOT2 ( target [ X_AXIS ] - offx , target [ Y_AXIS ] - offy ) ;
if ( dist_2 > delta_max_radius_2 ) {
const float ratio = ( delta_max_radius ) / SQRT ( dist_2 ) ; // 200 / 300 = 0.66
target [ X_AXIS ] * = ratio ;
target [ Y_AXIS ] * = ratio ;
}
2019-03-09 05:13:24 +01:00
2019-03-13 11:48:36 +01:00
# else
2019-03-09 05:13:24 +01:00
2019-03-13 11:48:36 +01:00
# if !HAS_SOFTWARE_ENDSTOPS || ENABLED(MIN_SOFTWARE_ENDSTOP_X)
NOLESS ( target [ X_AXIS ] , soft_endstop [ X_AXIS ] . min ) ;
# endif
# if !HAS_SOFTWARE_ENDSTOPS || ENABLED(MAX_SOFTWARE_ENDSTOP_X)
NOMORE ( target [ X_AXIS ] , soft_endstop [ X_AXIS ] . max ) ;
# endif
# if !HAS_SOFTWARE_ENDSTOPS || ENABLED(MIN_SOFTWARE_ENDSTOP_Y)
NOLESS ( target [ Y_AXIS ] , soft_endstop [ Y_AXIS ] . min ) ;
# endif
# if !HAS_SOFTWARE_ENDSTOPS || ENABLED(MAX_SOFTWARE_ENDSTOP_Y)
NOMORE ( target [ Y_AXIS ] , soft_endstop [ Y_AXIS ] . max ) ;
# endif
2019-03-09 05:13:24 +01:00
# endif
2019-03-13 11:48:36 +01:00
# if !HAS_SOFTWARE_ENDSTOPS || ENABLED(MIN_SOFTWARE_ENDSTOP_Z)
NOLESS ( target [ Z_AXIS ] , soft_endstop [ Z_AXIS ] . min ) ;
2019-03-09 05:13:24 +01:00
# endif
2019-03-13 11:48:36 +01:00
# if !HAS_SOFTWARE_ENDSTOPS || ENABLED(MAX_SOFTWARE_ENDSTOP_Z)
NOMORE ( target [ Z_AXIS ] , soft_endstop [ Z_AXIS ] . max ) ;
2019-03-09 05:13:24 +01:00
# endif
2019-03-13 11:48:36 +01:00
}
2019-03-09 05:13:24 +01:00
2019-03-13 11:48:36 +01:00
# endif // HAS_SOFTWARE_ENDSTOPS
2017-09-08 05:33:16 +02:00
2017-12-09 09:11:31 +01:00
# if !UBL_SEGMENTED
2017-11-09 02:49:51 +01:00
# if IS_KINEMATIC
2017-09-08 05:33:16 +02:00
2018-04-05 22:47:56 +02:00
# if IS_SCARA
/**
* Before raising this value , use M665 S [ seg_per_sec ] to decrease
* the number of segments - per - second . Default is 200. Some deltas
* do better with 160 or lower . It would be good to know how many
* segments - per - second are actually possible for SCARA on AVR .
*
* Longer segments result in less kinematic overhead
* but may produce jagged lines . Try 0.5 mm , 1.0 mm , and 2.0 mm
* and compare the difference .
*/
2018-07-01 22:20:28 +02:00
# define SCARA_MIN_SEGMENT_LENGTH 0.5f
2017-09-08 22:35:25 +02:00
# endif
2017-09-08 05:33:16 +02:00
/**
* Prepare a linear move in a DELTA or SCARA setup .
*
2017-12-09 09:10:54 +01:00
* Called from prepare_move_to_destination as the
* default Delta / SCARA segmenter .
*
2017-09-08 05:33:16 +02:00
* This calls planner . buffer_line several times , adding
* small incremental moves for DELTA or SCARA .
2017-11-09 02:49:51 +01:00
*
* For Unified Bed Leveling ( Delta or Segmented Cartesian )
* the ubl . prepare_segmented_line_to method replaces this .
2017-12-09 09:10:54 +01:00
*
* For Auto Bed Leveling ( Bilinear ) with SEGMENT_LEVELED_MOVES
* this is replaced by segmented_line_to_destination below .
2017-09-08 05:33:16 +02:00
*/
2017-12-09 09:10:54 +01:00
inline bool prepare_kinematic_move_to ( const float ( & rtarget ) [ XYZE ] ) {
2017-09-08 05:33:16 +02:00
// Get the top feedrate of the move in the XY plane
const float _feedrate_mm_s = MMS_SCALED ( feedrate_mm_s ) ;
2017-11-29 22:30:42 +01:00
const float xdiff = rtarget [ X_AXIS ] - current_position [ X_AXIS ] ,
ydiff = rtarget [ Y_AXIS ] - current_position [ Y_AXIS ] ;
2017-09-08 05:33:16 +02:00
// If the move is only in Z/E don't split up the move
2017-11-29 22:30:42 +01:00
if ( ! xdiff & & ! ydiff ) {
2018-09-17 04:24:15 +02:00
planner . buffer_line ( rtarget , _feedrate_mm_s , active_extruder ) ;
2017-12-25 05:56:23 +01:00
return false ; // caller will update current_position
2017-09-08 05:33:16 +02:00
}
// Fail if attempting move outside printable radius
2017-11-03 05:59:42 +01:00
if ( ! position_is_reachable ( rtarget [ X_AXIS ] , rtarget [ Y_AXIS ] ) ) return true ;
2017-09-08 05:33:16 +02:00
2017-11-29 22:30:42 +01:00
// Remaining cartesian distances
const float zdiff = rtarget [ Z_AXIS ] - current_position [ Z_AXIS ] ,
ediff = rtarget [ E_AXIS ] - current_position [ E_AXIS ] ;
2017-09-08 05:33:16 +02:00
// Get the linear distance in XYZ
2017-11-29 22:30:42 +01:00
float cartesian_mm = SQRT ( sq ( xdiff ) + sq ( ydiff ) + sq ( zdiff ) ) ;
2017-09-08 05:33:16 +02:00
// If the move is very short, check the E move distance
2018-05-13 08:10:34 +02:00
if ( UNEAR_ZERO ( cartesian_mm ) ) cartesian_mm = ABS ( ediff ) ;
2017-09-08 05:33:16 +02:00
// No E move either? Game over.
if ( UNEAR_ZERO ( cartesian_mm ) ) return true ;
// Minimum number of seconds to move the given distance
const float seconds = cartesian_mm / _feedrate_mm_s ;
// The number of segments-per-second times the duration
// gives the number of segments
uint16_t segments = delta_segments_per_second * seconds ;
2018-04-05 22:47:56 +02:00
// For SCARA enforce a minimum segment size
2017-09-08 05:33:16 +02:00
# if IS_SCARA
2019-09-14 10:05:10 +02:00
NOMORE ( segments , cartesian_mm * RECIPROCAL ( SCARA_MIN_SEGMENT_LENGTH ) ) ;
2017-09-08 05:33:16 +02:00
# endif
// At least one segment is required
2018-05-18 01:40:22 +02:00
NOLESS ( segments , 1U ) ;
2017-09-08 05:33:16 +02:00
// The approximate length of each segment
2018-07-01 22:20:28 +02:00
const float inv_segments = 1.0f / float ( segments ) ,
2017-09-08 05:33:16 +02:00
segment_distance [ XYZE ] = {
2017-11-29 22:30:42 +01:00
xdiff * inv_segments ,
ydiff * inv_segments ,
zdiff * inv_segments ,
ediff * inv_segments
2018-09-17 04:24:15 +02:00
} ,
cartesian_segment_mm = cartesian_mm * inv_segments ;
2018-09-18 00:30:04 +02:00
2018-09-17 04:24:15 +02:00
# if ENABLED(SCARA_FEEDRATE_SCALING)
const float inv_duration = _feedrate_mm_s / cartesian_segment_mm ;
2018-04-05 22:47:56 +02:00
# endif
2017-09-08 05:33:16 +02:00
2018-04-05 22:47:56 +02:00
/*
SERIAL_ECHOPAIR ( " mm= " , cartesian_mm ) ;
SERIAL_ECHOPAIR ( " seconds= " , seconds ) ;
SERIAL_ECHOPAIR ( " segments= " , segments ) ;
2018-09-17 04:24:15 +02:00
SERIAL_ECHOPAIR ( " segment_mm= " , cartesian_segment_mm ) ;
2018-06-30 20:44:27 +02:00
SERIAL_EOL ( ) ;
2018-04-05 22:47:56 +02:00
//*/
2018-09-17 04:24:15 +02:00
// Get the current position as starting point
2017-11-03 05:59:42 +01:00
float raw [ XYZE ] ;
COPY ( raw , current_position ) ;
2017-09-08 05:33:16 +02:00
// Calculate and execute the segments
2017-12-02 04:43:44 +01:00
while ( - - segments ) {
2017-11-28 17:03:51 +01:00
static millis_t next_idle_ms = millis ( ) + 200UL ;
thermalManager . manage_heater ( ) ; // This returns immediately if not really needed.
if ( ELAPSED ( millis ( ) , next_idle_ms ) ) {
next_idle_ms = millis ( ) + 200UL ;
idle ( ) ;
}
2017-11-03 05:59:42 +01:00
LOOP_XYZE ( i ) raw [ i ] + = segment_distance [ i ] ;
2017-12-22 04:43:39 +01:00
2018-09-17 04:24:15 +02:00
if ( ! planner . buffer_line ( raw , _feedrate_mm_s , active_extruder , cartesian_segment_mm
# if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
# endif
) )
break ;
2017-09-08 05:33:16 +02:00
}
2017-12-22 04:43:39 +01:00
// Ensure last segment arrives at target location.
2018-09-17 04:24:15 +02:00
planner . buffer_line ( rtarget , _feedrate_mm_s , active_extruder , cartesian_segment_mm
# if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
# endif
) ;
2017-09-08 05:33:16 +02:00
2017-12-25 05:56:23 +01:00
return false ; // caller will update current_position
2017-09-08 05:33:16 +02:00
}
2017-11-09 02:49:51 +01:00
# else // !IS_KINEMATIC
2017-09-08 05:33:16 +02:00
2017-11-29 22:30:42 +01:00
# if ENABLED(SEGMENT_LEVELED_MOVES)
/**
* Prepare a segmented move on a CARTESIAN setup .
*
* This calls planner . buffer_line several times , adding
* small incremental moves . This allows the planner to
* apply more detailed bed leveling to the full move .
*/
inline void segmented_line_to_destination ( const float & fr_mm_s , const float segment_size = LEVELED_SEGMENT_LENGTH ) {
const float xdiff = destination [ X_AXIS ] - current_position [ X_AXIS ] ,
ydiff = destination [ Y_AXIS ] - current_position [ Y_AXIS ] ;
// If the move is only in Z/E don't split up the move
if ( ! xdiff & & ! ydiff ) {
2018-09-17 04:24:15 +02:00
planner . buffer_line ( destination , fr_mm_s , active_extruder ) ;
2017-11-29 22:30:42 +01:00
return ;
}
// Remaining cartesian distances
const float zdiff = destination [ Z_AXIS ] - current_position [ Z_AXIS ] ,
ediff = destination [ E_AXIS ] - current_position [ E_AXIS ] ;
// Get the linear distance in XYZ
// If the move is very short, check the E move distance
// No E move either? Game over.
float cartesian_mm = SQRT ( sq ( xdiff ) + sq ( ydiff ) + sq ( zdiff ) ) ;
2018-05-13 08:10:34 +02:00
if ( UNEAR_ZERO ( cartesian_mm ) ) cartesian_mm = ABS ( ediff ) ;
2017-11-29 22:30:42 +01:00
if ( UNEAR_ZERO ( cartesian_mm ) ) return ;
// The length divided by the segment size
// At least one segment is required
uint16_t segments = cartesian_mm / segment_size ;
2018-05-18 01:40:22 +02:00
NOLESS ( segments , 1U ) ;
2017-11-29 22:30:42 +01:00
// The approximate length of each segment
2018-07-01 22:20:28 +02:00
const float inv_segments = 1.0f / float ( segments ) ,
2018-02-04 07:26:05 +01:00
cartesian_segment_mm = cartesian_mm * inv_segments ,
2017-11-29 22:30:42 +01:00
segment_distance [ XYZE ] = {
xdiff * inv_segments ,
ydiff * inv_segments ,
zdiff * inv_segments ,
ediff * inv_segments
} ;
2018-09-17 04:24:15 +02:00
# if ENABLED(SCARA_FEEDRATE_SCALING)
const float inv_duration = _feedrate_mm_s / cartesian_segment_mm ;
# endif
2017-11-29 22:30:42 +01:00
// SERIAL_ECHOPAIR("mm=", cartesian_mm);
// SERIAL_ECHOLNPAIR(" segments=", segments);
2018-02-04 07:26:05 +01:00
// SERIAL_ECHOLNPAIR(" segment_mm=", cartesian_segment_mm);
2017-11-29 22:30:42 +01:00
// Get the raw current position as starting point
float raw [ XYZE ] ;
COPY ( raw , current_position ) ;
// Calculate and execute the segments
2017-12-02 04:43:44 +01:00
while ( - - segments ) {
2017-11-29 22:30:42 +01:00
static millis_t next_idle_ms = millis ( ) + 200UL ;
thermalManager . manage_heater ( ) ; // This returns immediately if not really needed.
if ( ELAPSED ( millis ( ) , next_idle_ms ) ) {
next_idle_ms = millis ( ) + 200UL ;
idle ( ) ;
}
LOOP_XYZE ( i ) raw [ i ] + = segment_distance [ i ] ;
2018-09-17 04:24:15 +02:00
if ( ! planner . buffer_line ( raw , fr_mm_s , active_extruder , cartesian_segment_mm
# if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
# endif
) )
2018-05-09 07:17:53 +02:00
break ;
2017-11-29 22:30:42 +01:00
}
// Since segment_distance is only approximate,
// the final move must be to the exact destination.
2018-09-17 04:24:15 +02:00
planner . buffer_line ( destination , fr_mm_s , active_extruder , cartesian_segment_mm
# if ENABLED(SCARA_FEEDRATE_SCALING)
, inv_duration
# endif
) ;
2017-11-29 22:30:42 +01:00
}
# endif // SEGMENT_LEVELED_MOVES
2017-09-08 05:33:16 +02:00
/**
* Prepare a linear move in a Cartesian setup .
2017-11-09 02:49:51 +01:00
*
* When a mesh - based leveling system is active , moves are segmented
* according to the configuration of the leveling system .
2017-09-08 05:33:16 +02:00
*
2017-10-14 00:21:25 +02:00
* Returns true if current_position [ ] was set to destination [ ]
2017-09-08 05:33:16 +02:00
*/
inline bool prepare_move_to_destination_cartesian ( ) {
2017-11-09 02:49:51 +01:00
# if HAS_MESH
2017-11-29 22:30:42 +01:00
if ( planner . leveling_active & & planner . leveling_active_at_z ( destination [ Z_AXIS ] ) ) {
2017-11-08 00:13:53 +01:00
# if ENABLED(AUTO_BED_LEVELING_UBL)
2017-11-09 02:49:51 +01:00
ubl . line_to_destination_cartesian ( MMS_SCALED ( feedrate_mm_s ) , active_extruder ) ; // UBL's motion routine needs to know about
return true ; // all moves, including Z-only moves.
2017-11-29 22:30:42 +01:00
# elif ENABLED(SEGMENT_LEVELED_MOVES)
segmented_line_to_destination ( MMS_SCALED ( feedrate_mm_s ) ) ;
2017-12-25 05:56:23 +01:00
return false ; // caller will update current_position
2017-11-08 00:13:53 +01:00
# else
2017-11-09 02:49:51 +01:00
/**
* For MBL and ABL - BILINEAR only segment moves when X or Y are involved .
* Otherwise fall through to do a direct single move .
*/
2017-11-08 00:13:53 +01:00
if ( current_position [ X_AXIS ] ! = destination [ X_AXIS ] | | current_position [ Y_AXIS ] ! = destination [ Y_AXIS ] ) {
# if ENABLED(MESH_BED_LEVELING)
2018-05-13 23:48:42 +02:00
mbl . line_to_destination ( MMS_SCALED ( feedrate_mm_s ) ) ;
2017-11-08 00:13:53 +01:00
# elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
2017-11-09 02:49:51 +01:00
bilinear_line_to_destination ( MMS_SCALED ( feedrate_mm_s ) ) ;
2017-11-08 00:13:53 +01:00
# endif
return true ;
}
# endif
2017-11-09 02:49:51 +01:00
}
# endif // HAS_MESH
2017-10-14 00:21:25 +02:00
2017-11-09 05:13:33 +01:00
buffer_line_to_destination ( MMS_SCALED ( feedrate_mm_s ) ) ;
2017-12-25 05:56:23 +01:00
return false ; // caller will update current_position
2017-09-08 05:33:16 +02:00
}
2017-11-09 02:49:51 +01:00
# endif // !IS_KINEMATIC
2017-12-09 09:11:31 +01:00
# endif // !UBL_SEGMENTED
2017-09-08 05:33:16 +02:00
2019-03-13 06:42:50 +01:00
# if HAS_DUPLICATION_MODE
2019-03-16 04:46:27 +01:00
bool extruder_duplication_enabled ,
mirrored_duplication_mode ;
2019-04-04 09:44:07 +02:00
# if ENABLED(MULTI_NOZZLE_DUPLICATION)
uint8_t duplication_e_mask ; // = 0
# endif
2017-09-08 05:33:16 +02:00
# endif
# if ENABLED(DUAL_X_CARRIAGE)
DualXMode dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE ;
float inactive_extruder_x_pos = X2_MAX_POS , // used in mode 0 & 1
raised_parked_position [ XYZE ] , // used in mode 1
duplicate_extruder_x_offset = DEFAULT_DUPLICATION_X_OFFSET ; // used in mode 2
bool active_extruder_parked = false ; // used in mode 1 & 2
millis_t delayed_move_time = 0 ; // used in mode 1
int16_t duplicate_extruder_temp_offset = 0 ; // used in mode 2
float x_home_pos ( const int extruder ) {
if ( extruder = = 0 )
2017-11-03 05:59:42 +01:00
return base_home_pos ( X_AXIS ) ;
2017-09-08 05:33:16 +02:00
else
/**
* In dual carriage mode the extruder offset provides an override of the
* second X - carriage position when homed - otherwise X2_HOME_POS is used .
* This allows soft recalibration of the second extruder home position
* without firmware reflash ( through the M218 command ) .
*/
2017-11-03 05:59:42 +01:00
return hotend_offset [ X_AXIS ] [ 1 ] > 0 ? hotend_offset [ X_AXIS ] [ 1 ] : X2_HOME_POS ;
2017-09-08 05:33:16 +02:00
}
/**
* Prepare a linear move in a dual X axis setup
2017-10-14 00:21:25 +02:00
*
* Return true if current_position [ ] was set to destination [ ]
2017-09-08 05:33:16 +02:00
*/
2017-12-09 15:00:47 +01:00
inline bool dual_x_carriage_unpark ( ) {
2017-09-08 05:33:16 +02:00
if ( active_extruder_parked ) {
switch ( dual_x_carriage_mode ) {
case DXC_FULL_CONTROL_MODE :
break ;
case DXC_AUTO_PARK_MODE :
if ( current_position [ E_AXIS ] = = destination [ E_AXIS ] ) {
// This is a travel move (with no extrusion)
// Skip it, but keep track of the current position
// (so it can be used as the start of the next non-travel move)
if ( delayed_move_time ! = 0xFFFFFFFFUL ) {
2017-10-21 18:42:26 +02:00
set_current_from_destination ( ) ;
2017-09-08 05:33:16 +02:00
NOLESS ( raised_parked_position [ Z_AXIS ] , destination [ Z_AXIS ] ) ;
delayed_move_time = millis ( ) ;
return true ;
}
}
// unpark extruder: 1) raise, 2) move into starting XY position, 3) lower
2018-09-02 17:18:59 +02:00
# define CUR_X current_position[X_AXIS]
# define CUR_Y current_position[Y_AXIS]
# define CUR_Z current_position[Z_AXIS]
# define CUR_E current_position[E_AXIS]
# define RAISED_X raised_parked_position[X_AXIS]
# define RAISED_Y raised_parked_position[Y_AXIS]
# define RAISED_Z raised_parked_position[Z_AXIS]
2018-10-10 16:45:20 +02:00
if ( planner . buffer_line ( RAISED_X , RAISED_Y , RAISED_Z , CUR_E , planner . settings . max_feedrate_mm_s [ Z_AXIS ] , active_extruder ) )
2018-09-04 06:15:31 +02:00
if ( planner . buffer_line ( CUR_X , CUR_Y , RAISED_Z , CUR_E , PLANNER_XY_FEEDRATE ( ) , active_extruder ) )
2018-10-10 16:45:20 +02:00
planner . buffer_line ( CUR_X , CUR_Y , CUR_Z , CUR_E , planner . settings . max_feedrate_mm_s [ Z_AXIS ] , active_extruder ) ;
2017-09-08 05:33:16 +02:00
delayed_move_time = 0 ;
active_extruder_parked = false ;
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " Clear active_extruder_parked " ) ;
2017-09-08 05:33:16 +02:00
break ;
2019-03-16 04:46:27 +01:00
case DXC_MIRRORED_MODE :
2017-09-08 05:33:16 +02:00
case DXC_DUPLICATION_MODE :
if ( active_extruder = = 0 ) {
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " Set planner X " , inactive_extruder_x_pos , " ... Line to X " , current_position [ X_AXIS ] + duplicate_extruder_x_offset ) ;
2017-09-08 05:33:16 +02:00
// move duplicate extruder into correct duplication position.
2018-05-12 16:59:11 +02:00
planner . set_position_mm ( inactive_extruder_x_pos , current_position [ Y_AXIS ] , current_position [ Z_AXIS ] , current_position [ E_AXIS ] ) ;
2018-09-17 08:06:22 +02:00
2018-05-09 07:17:53 +02:00
if ( ! planner . buffer_line (
2018-09-17 08:06:22 +02:00
dual_x_carriage_mode = = DXC_DUPLICATION_MODE ? duplicate_extruder_x_offset + current_position [ X_AXIS ] : inactive_extruder_x_pos ,
current_position [ Y_AXIS ] , current_position [ Z_AXIS ] , current_position [ E_AXIS ] ,
2018-10-10 16:45:20 +02:00
planner . settings . max_feedrate_mm_s [ X_AXIS ] , 1
2018-09-17 08:06:22 +02:00
)
2018-05-09 07:17:53 +02:00
) break ;
2018-05-12 08:38:02 +02:00
planner . synchronize ( ) ;
2018-09-17 04:24:15 +02:00
sync_plan_position ( ) ;
2017-09-08 05:33:16 +02:00
extruder_duplication_enabled = true ;
active_extruder_parked = false ;
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " Set extruder_duplication_enabled \n Clear active_extruder_parked " ) ;
2017-09-08 05:33:16 +02:00
}
2019-03-14 08:25:42 +01:00
else if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " Active extruder not 0 " ) ;
2017-09-08 05:33:16 +02:00
break ;
}
}
2018-09-17 08:06:22 +02:00
stepper . set_directions ( ) ;
2017-12-09 15:00:47 +01:00
return false ;
2017-09-08 05:33:16 +02:00
}
# endif // DUAL_X_CARRIAGE
/**
* Prepare a single move and get ready for the next one
*
* This may result in several calls to planner . buffer_line to
* do smaller moves for DELTA , SCARA , mesh moves , etc .
2017-11-11 03:49:37 +01:00
*
* Make sure current_position [ E ] and destination [ E ] are good
* before calling or cold / lengthy extrusion may get missed .
2017-09-08 05:33:16 +02:00
*/
void prepare_move_to_destination ( ) {
2019-03-13 11:48:36 +01:00
apply_motion_limits ( destination ) ;
2017-09-08 05:33:16 +02:00
2019-03-17 05:43:06 +01:00
# if EITHER(PREVENT_COLD_EXTRUSION, PREVENT_LENGTHY_EXTRUDE)
2017-09-08 05:33:16 +02:00
if ( ! DEBUGGING ( DRYRUN ) ) {
if ( destination [ E_AXIS ] ! = current_position [ E_AXIS ] ) {
2017-11-10 05:50:32 +01:00
# if ENABLED(PREVENT_COLD_EXTRUSION)
if ( thermalManager . tooColdToExtrude ( active_extruder ) ) {
current_position [ E_AXIS ] = destination [ E_AXIS ] ; // Behave as if the move really took place, but ignore E part
2018-11-29 23:58:58 +01:00
SERIAL_ECHO_MSG ( MSG_ERR_COLD_EXTRUDE_STOP ) ;
2017-11-10 05:50:32 +01:00
}
# endif // PREVENT_COLD_EXTRUSION
2017-09-08 05:33:16 +02:00
# if ENABLED(PREVENT_LENGTHY_EXTRUDE)
2019-05-02 04:55:58 +02:00
const float e_delta = ABS ( destination [ E_AXIS ] - current_position [ E_AXIS ] ) * planner . e_factor [ active_extruder ] ;
if ( e_delta > ( EXTRUDE_MAXLENGTH ) ) {
# if ENABLED(MIXING_EXTRUDER)
bool ignore_e = false ;
float collector [ MIXING_STEPPERS ] ;
mixer . refresh_collector ( 1.0 , mixer . get_current_vtool ( ) , collector ) ;
MIXER_STEPPER_LOOP ( e )
if ( e_delta * collector [ e ] > ( EXTRUDE_MAXLENGTH ) ) { ignore_e = true ; break ; }
# else
constexpr bool ignore_e = true ;
# endif
if ( ignore_e ) {
current_position [ E_AXIS ] = destination [ E_AXIS ] ; // Behave as if the move really took place, but ignore E part
SERIAL_ECHO_MSG ( MSG_ERR_LONG_EXTRUDE_STOP ) ;
}
2017-09-08 05:33:16 +02:00
}
2017-11-10 05:50:32 +01:00
# endif // PREVENT_LENGTHY_EXTRUDE
2017-09-08 05:33:16 +02:00
}
}
2017-11-10 05:50:32 +01:00
# endif // PREVENT_COLD_EXTRUSION || PREVENT_LENGTHY_EXTRUDE
2017-09-08 05:33:16 +02:00
2017-12-09 15:00:47 +01:00
# if ENABLED(DUAL_X_CARRIAGE)
if ( dual_x_carriage_unpark ( ) ) return ;
# endif
2017-09-08 05:33:16 +02:00
if (
2017-12-09 09:11:31 +01:00
# if UBL_SEGMENTED
2018-09-17 08:06:22 +02:00
//ubl.prepare_segmented_line_to(destination, MMS_SCALED(feedrate_mm_s)) // This doesn't seem to work correctly on UBL.
# if IS_KINEMATIC // Use Kinematic / Cartesian cases as a workaround for now.
ubl . prepare_segmented_line_to ( destination , MMS_SCALED ( feedrate_mm_s ) )
2018-09-02 17:18:59 +02:00
# else
prepare_move_to_destination_cartesian ( )
# endif
2017-09-08 05:33:16 +02:00
# elif IS_KINEMATIC
prepare_kinematic_move_to ( destination )
# else
prepare_move_to_destination_cartesian ( )
# endif
) return ;
2017-10-21 18:42:26 +02:00
set_current_from_destination ( ) ;
2017-09-08 05:33:16 +02:00
}
2017-09-08 22:35:25 +02:00
2019-09-26 04:01:29 +02:00
uint8_t axes_need_homing ( uint8_t axis_bits /*=0x07*/ ) {
2019-03-12 02:57:54 +01:00
# if ENABLED(HOME_AFTER_DEACTIVATE)
2019-09-26 04:01:29 +02:00
# define HOMED_FLAGS axis_known_position
2019-03-12 02:57:54 +01:00
# else
2019-09-26 04:01:29 +02:00
# define HOMED_FLAGS axis_homed
2019-03-12 02:57:54 +01:00
# endif
2019-09-26 04:01:29 +02:00
// Clear test bits that are homed
if ( TEST ( axis_bits , X_AXIS ) & & TEST ( HOMED_FLAGS , X_AXIS ) ) CBI ( axis_bits , X_AXIS ) ;
if ( TEST ( axis_bits , Y_AXIS ) & & TEST ( HOMED_FLAGS , Y_AXIS ) ) CBI ( axis_bits , Y_AXIS ) ;
if ( TEST ( axis_bits , Z_AXIS ) & & TEST ( HOMED_FLAGS , Z_AXIS ) ) CBI ( axis_bits , Z_AXIS ) ;
return axis_bits ;
}
2019-03-12 02:57:54 +01:00
2019-09-26 04:01:29 +02:00
bool axis_unhomed_error ( uint8_t axis_bits /*=0x07*/ ) {
if ( ( axis_bits = axes_need_homing ( axis_bits ) ) ) {
static const char home_first [ ] PROGMEM = MSG_HOME_FIRST ;
char msg [ sizeof ( home_first ) ] ;
sprintf_P ( msg , home_first ,
TEST ( axis_bits , X_AXIS ) ? " X " : " " ,
TEST ( axis_bits , Y_AXIS ) ? " Y " : " " ,
TEST ( axis_bits , Z_AXIS ) ? " Z " : " "
) ;
SERIAL_ECHO_START ( ) ;
2019-09-26 05:26:08 +02:00
SERIAL_ECHOLN ( msg ) ;
2019-07-17 10:12:39 +02:00
# if HAS_DISPLAY
2019-09-26 04:01:29 +02:00
ui . set_status ( msg ) ;
2017-09-08 22:35:25 +02:00
# endif
2019-03-12 02:57:54 +01:00
return true ;
2017-09-08 22:35:25 +02:00
}
2019-03-12 02:57:54 +01:00
return false ;
}
2017-09-08 22:35:25 +02:00
/**
2018-04-30 10:35:07 +02:00
* Homing bump feedrate ( mm / s )
2017-09-08 22:35:25 +02:00
*/
2019-02-14 12:25:29 +01:00
float get_homing_bump_feedrate ( const AxisEnum axis ) {
2018-04-28 15:50:23 +02:00
# if HOMING_Z_WITH_PROBE
2018-04-30 10:35:07 +02:00
if ( axis = = Z_AXIS ) return MMM_TO_MMS ( Z_PROBE_SPEED_SLOW ) ;
2018-04-28 15:50:23 +02:00
# endif
2017-09-08 22:35:25 +02:00
static const uint8_t homing_bump_divisor [ ] PROGMEM = HOMING_BUMP_DIVISOR ;
uint8_t hbd = pgm_read_byte ( & homing_bump_divisor [ axis ] ) ;
if ( hbd < 1 ) {
hbd = 10 ;
2018-11-29 23:58:58 +01:00
SERIAL_ECHO_MSG ( " Warning: Homing Bump Divisor < 1 " ) ;
2017-09-08 22:35:25 +02:00
}
return homing_feedrate ( axis ) / hbd ;
}
2018-03-01 08:37:31 +01:00
# if ENABLED(SENSORLESS_HOMING)
/**
* Set sensorless homing if the axis has it , accounting for Core Kinematics .
*/
2018-12-07 22:34:21 +01:00
sensorless_t start_sensorless_homing_per_axis ( const AxisEnum axis ) {
2019-07-24 08:52:36 +02:00
sensorless_t stealth_states { false } ;
2018-12-07 22:34:21 +01:00
switch ( axis ) {
default : break ;
# if X_SENSORLESS
case X_AXIS :
stealth_states . x = tmc_enable_stallguard ( stepperX ) ;
2019-02-02 01:09:01 +01:00
# if AXIS_HAS_STALLGUARD(X2)
stealth_states . x2 = tmc_enable_stallguard ( stepperX2 ) ;
# endif
2018-12-07 22:34:21 +01:00
# if CORE_IS_XY && Y_SENSORLESS
stealth_states . y = tmc_enable_stallguard ( stepperY ) ;
# elif CORE_IS_XZ && Z_SENSORLESS
stealth_states . z = tmc_enable_stallguard ( stepperZ ) ;
# endif
break ;
# endif
# if Y_SENSORLESS
case Y_AXIS :
stealth_states . y = tmc_enable_stallguard ( stepperY ) ;
2019-02-02 01:09:01 +01:00
# if AXIS_HAS_STALLGUARD(Y2)
stealth_states . y2 = tmc_enable_stallguard ( stepperY2 ) ;
# endif
2018-12-07 22:34:21 +01:00
# if CORE_IS_XY && X_SENSORLESS
stealth_states . x = tmc_enable_stallguard ( stepperX ) ;
# elif CORE_IS_YZ && Z_SENSORLESS
stealth_states . z = tmc_enable_stallguard ( stepperZ ) ;
# endif
break ;
# endif
# if Z_SENSORLESS
case Z_AXIS :
stealth_states . z = tmc_enable_stallguard ( stepperZ ) ;
2019-02-02 01:09:01 +01:00
# if AXIS_HAS_STALLGUARD(Z2)
stealth_states . z2 = tmc_enable_stallguard ( stepperZ2 ) ;
# endif
# if AXIS_HAS_STALLGUARD(Z3)
stealth_states . z3 = tmc_enable_stallguard ( stepperZ3 ) ;
# endif
2018-12-07 22:34:21 +01:00
# if CORE_IS_XZ && X_SENSORLESS
stealth_states . x = tmc_enable_stallguard ( stepperX ) ;
# elif CORE_IS_YZ && Y_SENSORLESS
stealth_states . y = tmc_enable_stallguard ( stepperY ) ;
# endif
break ;
# endif
}
2019-08-05 05:22:58 +02:00
# if ENABLED(SPI_ENDSTOPS)
switch ( axis ) {
# if X_SPI_SENSORLESS
case X_AXIS : endstops . tmc_spi_homing . x = true ; break ;
# endif
# if Y_SPI_SENSORLESS
case Y_AXIS : endstops . tmc_spi_homing . y = true ; break ;
# endif
# if Z_SPI_SENSORLESS
case Z_AXIS : endstops . tmc_spi_homing . z = true ; break ;
# endif
default : break ;
}
# endif
# if ENABLED(IMPROVE_HOMING_RELIABILITY)
sg_guard_period = millis ( ) + default_sg_guard_duration ;
# endif
2018-12-07 22:34:21 +01:00
return stealth_states ;
}
void end_sensorless_homing_per_axis ( const AxisEnum axis , sensorless_t enable_stealth ) {
2018-03-01 08:37:31 +01:00
switch ( axis ) {
2018-03-14 05:15:19 +01:00
default : break ;
2018-03-01 08:37:31 +01:00
# if X_SENSORLESS
case X_AXIS :
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperX , enable_stealth . x ) ;
2019-02-02 01:09:01 +01:00
# if AXIS_HAS_STALLGUARD(X2)
tmc_disable_stallguard ( stepperX2 , enable_stealth . x2 ) ;
# endif
2018-03-01 08:37:31 +01:00
# if CORE_IS_XY && Y_SENSORLESS
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperY , enable_stealth . y ) ;
2018-03-01 08:37:31 +01:00
# elif CORE_IS_XZ && Z_SENSORLESS
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperZ , enable_stealth . z ) ;
2018-03-01 08:37:31 +01:00
# endif
break ;
# endif
# if Y_SENSORLESS
case Y_AXIS :
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperY , enable_stealth . y ) ;
2019-02-02 01:09:01 +01:00
# if AXIS_HAS_STALLGUARD(Y2)
tmc_disable_stallguard ( stepperY2 , enable_stealth . y2 ) ;
# endif
2018-03-01 08:37:31 +01:00
# if CORE_IS_XY && X_SENSORLESS
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperX , enable_stealth . x ) ;
2018-03-01 08:37:31 +01:00
# elif CORE_IS_YZ && Z_SENSORLESS
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperZ , enable_stealth . z ) ;
2018-03-01 08:37:31 +01:00
# endif
break ;
# endif
# if Z_SENSORLESS
case Z_AXIS :
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperZ , enable_stealth . z ) ;
2019-02-02 01:09:01 +01:00
# if AXIS_HAS_STALLGUARD(Z2)
tmc_disable_stallguard ( stepperZ2 , enable_stealth . z2 ) ;
# endif
# if AXIS_HAS_STALLGUARD(Z3)
tmc_disable_stallguard ( stepperZ3 , enable_stealth . z3 ) ;
# endif
2018-03-01 08:37:31 +01:00
# if CORE_IS_XZ && X_SENSORLESS
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperX , enable_stealth . x ) ;
2018-03-01 08:37:31 +01:00
# elif CORE_IS_YZ && Y_SENSORLESS
2018-12-07 22:34:21 +01:00
tmc_disable_stallguard ( stepperY , enable_stealth . y ) ;
2018-03-01 08:37:31 +01:00
# endif
break ;
# endif
}
2019-08-05 05:22:58 +02:00
# if ENABLED(SPI_ENDSTOPS)
switch ( axis ) {
# if X_SPI_SENSORLESS
case X_AXIS : endstops . tmc_spi_homing . x = false ; break ;
# endif
# if Y_SPI_SENSORLESS
case Y_AXIS : endstops . tmc_spi_homing . y = false ; break ;
# endif
# if Z_SPI_SENSORLESS
case Z_AXIS : endstops . tmc_spi_homing . z = false ; break ;
# endif
default : break ;
}
# endif
2018-03-01 08:37:31 +01:00
}
# endif // SENSORLESS_HOMING
2017-09-08 22:35:25 +02:00
/**
* Home an individual linear axis
*/
2018-09-17 04:24:15 +02:00
void do_homing_move ( const AxisEnum axis , const float distance , const float fr_mm_s = 0.0 ) {
2017-09-08 22:35:25 +02:00
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) {
DEBUG_ECHOPAIR ( " >>> do_homing_move( " , axis_codes [ axis ] , " , " , distance , " , " ) ;
if ( fr_mm_s )
DEBUG_ECHO ( fr_mm_s ) ;
else
DEBUG_ECHOPAIR ( " [ " , homing_feedrate ( axis ) , " ] " ) ;
DEBUG_ECHOLNPGM ( " ) " ) ;
}
2017-09-08 22:35:25 +02:00
2018-04-24 00:13:01 +02:00
# if HOMING_Z_WITH_PROBE && HAS_HEATED_BED && ENABLED(WAIT_FOR_BED_HEATER)
2018-04-21 22:43:21 +02:00
// Wait for bed to heat back up between probing points
if ( axis = = Z_AXIS & & distance < 0 & & thermalManager . isHeatingBed ( ) ) {
serialprintPGM ( msg_wait_for_bed_heating ) ;
LCD_MESSAGEPGM ( MSG_BED_HEATING ) ;
2018-11-24 03:39:12 +01:00
thermalManager . wait_for_bed ( ) ;
2018-11-11 19:16:24 +01:00
ui . reset_status ( ) ;
2018-04-21 22:43:21 +02:00
}
# endif
2018-03-29 04:26:43 +02:00
// Only do some things when moving towards an endstop
const int8_t axis_home_dir =
# if ENABLED(DUAL_X_CARRIAGE)
( axis = = X_AXIS ) ? x_home_dir ( active_extruder ) :
# endif
home_dir ( axis ) ;
const bool is_home_dir = ( axis_home_dir > 0 ) = = ( distance > 0 ) ;
2017-09-08 22:35:25 +02:00
2018-12-07 22:34:21 +01:00
# if ENABLED(SENSORLESS_HOMING)
sensorless_t stealth_states ;
# endif
2018-03-29 04:26:43 +02:00
if ( is_home_dir ) {
2017-09-08 22:35:25 +02:00
2018-07-26 12:04:09 +02:00
# if HOMING_Z_WITH_PROBE && QUIET_PROBING
if ( axis = = Z_AXIS ) probing_pause ( true ) ;
# endif
2018-03-29 04:26:43 +02:00
// Disable stealthChop if used. Enable diag1 pin on driver.
# if ENABLED(SENSORLESS_HOMING)
2018-12-07 22:34:21 +01:00
stealth_states = start_sensorless_homing_per_axis ( axis ) ;
2018-03-29 04:26:43 +02:00
# endif
}
2018-02-08 11:20:44 +01:00
2017-09-08 22:35:25 +02:00
# if IS_SCARA
2018-09-17 04:24:15 +02:00
// Tell the planner the axis is at 0
current_position [ axis ] = 0 ;
sync_plan_position ( ) ;
2017-09-08 22:35:25 +02:00
current_position [ axis ] = distance ;
2018-09-17 04:24:15 +02:00
planner . buffer_line ( current_position , fr_mm_s ? fr_mm_s : homing_feedrate ( axis ) , active_extruder ) ;
2017-09-08 22:35:25 +02:00
# else
2018-09-17 04:24:15 +02:00
float target [ ABCE ] = { planner . get_axis_position_mm ( A_AXIS ) , planner . get_axis_position_mm ( B_AXIS ) , planner . get_axis_position_mm ( C_AXIS ) , planner . get_axis_position_mm ( E_AXIS ) } ;
target [ axis ] = 0 ;
planner . set_machine_position_mm ( target ) ;
target [ axis ] = distance ;
# if IS_KINEMATIC && ENABLED(JUNCTION_DEVIATION)
const float delta_mm_cart [ XYZE ] = { 0 , 0 , 0 , 0 } ;
# endif
// Set delta/cartesian axes directly
planner . buffer_segment ( target
# if IS_KINEMATIC && ENABLED(JUNCTION_DEVIATION)
, delta_mm_cart
# endif
, fr_mm_s ? fr_mm_s : homing_feedrate ( axis ) , active_extruder
) ;
2017-09-08 22:35:25 +02:00
# endif
2018-05-12 08:38:02 +02:00
planner . synchronize ( ) ;
2017-09-08 22:35:25 +02:00
2018-03-29 04:26:43 +02:00
if ( is_home_dir ) {
2017-09-08 22:35:25 +02:00
2018-07-26 12:04:09 +02:00
# if HOMING_Z_WITH_PROBE && QUIET_PROBING
if ( axis = = Z_AXIS ) probing_pause ( false ) ;
# endif
2017-09-08 22:35:25 +02:00
2018-07-01 04:54:07 +02:00
endstops . validate_homing_move ( ) ;
2017-09-08 22:35:25 +02:00
2018-03-29 04:26:43 +02:00
// Re-enable stealthChop if used. Disable diag1 pin on driver.
# if ENABLED(SENSORLESS_HOMING)
2018-12-07 22:34:21 +01:00
end_sensorless_homing_per_axis ( axis , stealth_states ) ;
2018-03-29 04:26:43 +02:00
# endif
}
2018-02-08 11:20:44 +01:00
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " <<< do_homing_move( " , axis_codes [ axis ] , " ) " ) ;
2017-09-08 22:35:25 +02:00
}
/**
* Set an axis ' current position to its home position ( after homing ) .
*
* For Core and Cartesian robots this applies one - to - one when an
* individual axis has been homed .
*
* DELTA should wait until all homing is done before setting the XYZ
* current_position to home , because homing is a single operation .
* In the case where the axis positions are already known and previously
* homed , DELTA could home to X or Y individually by moving either one
* to the center . However , homing Z always homes XY and Z .
*
* SCARA should wait until all XY homing is done before setting the XY
* current_position to home , because neither X nor Y is at home until
* both are at home . Z can however be homed individually .
*
* Callers must sync the planner position after calling this !
*/
void set_axis_is_at_home ( const AxisEnum axis ) {
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " >>> set_axis_is_at_home( " , axis_codes [ axis ] , " ) " ) ;
2017-09-08 22:35:25 +02:00
2018-06-12 04:29:31 +02:00
SBI ( axis_known_position , axis ) ;
SBI ( axis_homed , axis ) ;
2017-09-08 22:35:25 +02:00
# if ENABLED(DUAL_X_CARRIAGE)
if ( axis = = X_AXIS & & ( active_extruder = = 1 | | dual_x_carriage_mode = = DXC_DUPLICATION_MODE ) ) {
current_position [ X_AXIS ] = x_home_pos ( active_extruder ) ;
return ;
}
# endif
# if ENABLED(MORGAN_SCARA)
scara_set_axis_is_at_home ( axis ) ;
2017-11-09 05:10:08 +01:00
# elif ENABLED(DELTA)
2018-10-25 16:11:26 +02:00
current_position [ axis ] = ( axis = = Z_AXIS ? delta_height
# if HAS_BED_PROBE
2019-09-25 06:35:49 +02:00
- probe_offset [ Z_AXIS ]
2018-10-25 16:11:26 +02:00
# endif
: base_home_pos ( axis ) ) ;
2017-09-08 22:35:25 +02:00
# else
2017-11-03 05:59:42 +01:00
current_position [ axis ] = base_home_pos ( axis ) ;
2017-09-08 22:35:25 +02:00
# endif
/**
* Z Probe Z Homing ? Account for the probe ' s Z offset .
*/
# if HAS_BED_PROBE && Z_HOME_DIR < 0
if ( axis = = Z_AXIS ) {
# if HOMING_Z_WITH_PROBE
2019-09-25 06:35:49 +02:00
current_position [ Z_AXIS ] - = probe_offset [ Z_AXIS ] ;
2017-09-08 22:35:25 +02:00
2019-09-25 06:35:49 +02:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " *** Z HOMED WITH PROBE (Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) *** \n > probe_offset[Z_AXIS] = " , probe_offset [ Z_AXIS ] ) ;
2017-09-08 22:35:25 +02:00
2019-03-14 08:25:42 +01:00
# else
2017-09-08 22:35:25 +02:00
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " *** Z HOMED TO ENDSTOP *** " ) ;
2017-09-08 22:35:25 +02:00
# endif
}
# endif
2019-04-07 01:04:34 +02:00
# if ENABLED(I2C_POSITION_ENCODERS)
I2CPEM . homed ( axis ) ;
# endif
# if ENABLED(BABYSTEP_DISPLAY_TOTAL)
babystep . reset_total ( axis ) ;
# endif
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) {
# if HAS_HOME_OFFSET
DEBUG_ECHOLNPAIR ( " > home_offset[ " , axis_codes [ axis ] , " ] = " , home_offset [ axis ] ) ;
# endif
DEBUG_POS ( " " , current_position ) ;
DEBUG_ECHOLNPAIR ( " <<< set_axis_is_at_home( " , axis_codes [ axis ] , " ) " ) ;
}
2017-09-08 22:35:25 +02:00
}
2018-10-29 20:01:36 +01:00
/**
* Set an axis ' to be unhomed .
*/
void set_axis_is_not_at_home ( const AxisEnum axis ) {
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " >>> set_axis_is_not_at_home( " , axis_codes [ axis ] , " ) " ) ;
2018-10-29 20:01:36 +01:00
CBI ( axis_known_position , axis ) ;
CBI ( axis_homed , axis ) ;
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " <<< set_axis_is_not_at_home( " , axis_codes [ axis ] , " ) " ) ;
2018-10-29 20:01:36 +01:00
# if ENABLED(I2C_POSITION_ENCODERS)
I2CPEM . unhomed ( axis ) ;
# endif
}
2017-09-08 22:35:25 +02:00
/**
* Home an individual " raw axis " to its endstop .
* This applies to XYZ on Cartesian and Core robots , and
* to the individual ABC steppers on DELTA and SCARA .
*
* At the end of the procedure the axis is marked as
* homed and the current position of that axis is updated .
* Kinematic robots should wait till all axes are homed
* before updating the current position .
*/
void homeaxis ( const AxisEnum axis ) {
# if IS_SCARA
// Only Z homing (with probe) is permitted
if ( axis ! = Z_AXIS ) { BUZZ ( 100 , 880 ) ; return ; }
# else
2019-08-05 05:22:58 +02:00
# define _CAN_HOME(A) \
2018-05-13 10:44:24 +02:00
( axis = = _AXIS ( A ) & & ( ( A # # _MIN_PIN > - 1 & & A # # _HOME_DIR < 0 ) | | ( A # # _MAX_PIN > - 1 & & A # # _HOME_DIR > 0 ) ) )
2019-08-05 05:22:58 +02:00
# if X_SPI_SENSORLESS
# define CAN_HOME_X true
# else
# define CAN_HOME_X _CAN_HOME(X)
# endif
# if Y_SPI_SENSORLESS
# define CAN_HOME_Y true
# else
# define CAN_HOME_Y _CAN_HOME(Y)
# endif
# if Z_SPI_SENSORLESS
# define CAN_HOME_Z true
# else
# define CAN_HOME_Z _CAN_HOME(Z)
# endif
if ( ! CAN_HOME_X & & ! CAN_HOME_Y & & ! CAN_HOME_Z ) return ;
2017-09-08 22:35:25 +02:00
# endif
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " >>> homeaxis( " , axis_codes [ axis ] , " ) " ) ;
2017-09-08 22:35:25 +02:00
2018-06-03 03:39:00 +02:00
const int axis_home_dir = (
2017-09-08 22:35:25 +02:00
# if ENABLED(DUAL_X_CARRIAGE)
2018-06-03 03:39:00 +02:00
axis = = X_AXIS ? x_home_dir ( active_extruder ) :
2017-09-08 22:35:25 +02:00
# endif
2018-06-03 03:39:00 +02:00
home_dir ( axis )
) ;
2017-09-08 22:35:25 +02:00
// Homing Z towards the bed? Deploy the Z probe or endstop.
# if HOMING_Z_WITH_PROBE
2018-07-29 02:30:14 +02:00
if ( axis = = Z_AXIS & & DEPLOY_PROBE ( ) ) return ;
2017-09-08 22:35:25 +02:00
# endif
2017-10-29 09:43:44 +01:00
// Set flags for X, Y, Z motor locking
2019-02-06 11:59:22 +01:00
# if HAS_EXTRA_ENDSTOPS
2018-06-03 03:39:00 +02:00
switch ( axis ) {
# if ENABLED(X_DUAL_ENDSTOPS)
case X_AXIS :
# endif
# if ENABLED(Y_DUAL_ENDSTOPS)
case Y_AXIS :
# endif
2018-10-29 20:01:36 +01:00
# if Z_MULTI_ENDSTOPS
2018-06-19 18:55:49 +02:00
case Z_AXIS :
# endif
stepper . set_separate_multi_axis ( true ) ;
2018-06-03 03:39:00 +02:00
default : break ;
}
2017-09-08 22:35:25 +02:00
# endif
// Fast move towards endstop until triggered
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " Home 1 Fast: " ) ;
2018-07-29 02:30:14 +02:00
# if HOMING_Z_WITH_PROBE && ENABLED(BLTOUCH)
2019-05-18 02:10:18 +02:00
if ( axis = = Z_AXIS & & bltouch . deploy ( ) ) return ; // The initial DEPLOY
2018-07-29 02:30:14 +02:00
# endif
2018-09-17 04:24:15 +02:00
do_homing_move ( axis , 1.5f * max_length (
# if ENABLED(DELTA)
Z_AXIS
# else
axis
# endif
) * axis_home_dir
) ;
2018-07-28 01:30:08 +02:00
2019-05-18 02:10:18 +02:00
# if HOMING_Z_WITH_PROBE && ENABLED(BLTOUCH) && DISABLED(BLTOUCH_HS_MODE)
if ( axis = = Z_AXIS ) bltouch . stow ( ) ; // Intermediate STOW (in LOW SPEED MODE)
2018-07-26 12:04:09 +02:00
# endif
2017-09-08 22:35:25 +02:00
// When homing Z with probe respect probe clearance
const float bump = axis_home_dir * (
# if HOMING_Z_WITH_PROBE
2019-07-06 01:01:21 +02:00
( axis = = Z_AXIS & & ( Z_HOME_BUMP_MM ) ) ? _MAX ( Z_CLEARANCE_BETWEEN_PROBES , Z_HOME_BUMP_MM ) :
2017-09-08 22:35:25 +02:00
# endif
home_bump_mm ( axis )
) ;
// If a second homing move is configured...
if ( bump ) {
// Move away from the endstop by the axis HOME_BUMP_MM
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " Move Away: " ) ;
2018-04-30 10:35:07 +02:00
do_homing_move ( axis , - bump
# if HOMING_Z_WITH_PROBE
2018-05-01 06:49:07 +02:00
, axis = = Z_AXIS ? MMM_TO_MMS ( Z_PROBE_SPEED_FAST ) : 0.0
2018-04-30 10:35:07 +02:00
# endif
) ;
2017-09-08 22:35:25 +02:00
// Slow move towards endstop until triggered
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " Home 2 Slow: " ) ;
2018-07-26 12:04:09 +02:00
2019-05-18 02:10:18 +02:00
# if HOMING_Z_WITH_PROBE && ENABLED(BLTOUCH) && DISABLED(BLTOUCH_HS_MODE)
if ( axis = = Z_AXIS & & bltouch . deploy ( ) ) return ; // Intermediate DEPLOY (in LOW SPEED MODE)
2018-07-26 12:04:09 +02:00
# endif
2018-07-28 01:30:08 +02:00
2017-09-08 22:35:25 +02:00
do_homing_move ( axis , 2 * bump , get_homing_bump_feedrate ( axis ) ) ;
2018-07-29 02:30:14 +02:00
# if HOMING_Z_WITH_PROBE && ENABLED(BLTOUCH)
2019-05-18 02:10:18 +02:00
if ( axis = = Z_AXIS ) bltouch . stow ( ) ; // The final STOW
2018-07-29 02:30:14 +02:00
# endif
2017-09-08 22:35:25 +02:00
}
2019-02-06 11:59:22 +01:00
# if HAS_EXTRA_ENDSTOPS
2017-10-29 09:43:44 +01:00
const bool pos_dir = axis_home_dir > 0 ;
# if ENABLED(X_DUAL_ENDSTOPS)
if ( axis = = X_AXIS ) {
2018-06-19 18:55:49 +02:00
const float adj = ABS ( endstops . x2_endstop_adj ) ;
2018-07-14 06:12:43 +02:00
if ( adj ) {
2018-06-19 18:55:49 +02:00
if ( pos_dir ? ( endstops . x2_endstop_adj > 0 ) : ( endstops . x2_endstop_adj < 0 ) ) stepper . set_x_lock ( true ) ; else stepper . set_x2_lock ( true ) ;
2018-07-14 06:12:43 +02:00
do_homing_move ( axis , pos_dir ? - adj : adj ) ;
stepper . set_x_lock ( false ) ;
stepper . set_x2_lock ( false ) ;
}
2017-09-08 22:35:25 +02:00
}
2017-10-29 09:43:44 +01:00
# endif
# if ENABLED(Y_DUAL_ENDSTOPS)
if ( axis = = Y_AXIS ) {
2018-06-19 18:55:49 +02:00
const float adj = ABS ( endstops . y2_endstop_adj ) ;
2018-07-14 06:12:43 +02:00
if ( adj ) {
2018-06-19 18:55:49 +02:00
if ( pos_dir ? ( endstops . y2_endstop_adj > 0 ) : ( endstops . y2_endstop_adj < 0 ) ) stepper . set_y_lock ( true ) ; else stepper . set_y2_lock ( true ) ;
2018-07-14 06:12:43 +02:00
do_homing_move ( axis , pos_dir ? - adj : adj ) ;
stepper . set_y_lock ( false ) ;
stepper . set_y2_lock ( false ) ;
}
2017-10-29 09:43:44 +01:00
}
# endif
# if ENABLED(Z_DUAL_ENDSTOPS)
if ( axis = = Z_AXIS ) {
2018-06-19 18:55:49 +02:00
const float adj = ABS ( endstops . z2_endstop_adj ) ;
2018-07-14 06:12:43 +02:00
if ( adj ) {
2018-06-19 18:55:49 +02:00
if ( pos_dir ? ( endstops . z2_endstop_adj > 0 ) : ( endstops . z2_endstop_adj < 0 ) ) stepper . set_z_lock ( true ) ; else stepper . set_z2_lock ( true ) ;
2018-07-14 06:12:43 +02:00
do_homing_move ( axis , pos_dir ? - adj : adj ) ;
stepper . set_z_lock ( false ) ;
stepper . set_z2_lock ( false ) ;
}
2017-10-29 09:43:44 +01:00
}
# endif
2018-06-19 18:55:49 +02:00
# if ENABLED(Z_TRIPLE_ENDSTOPS)
if ( axis = = Z_AXIS ) {
// we push the function pointers for the stepper lock function into an array
void ( * lock [ 3 ] ) ( bool ) = { & stepper . set_z_lock , & stepper . set_z2_lock , & stepper . set_z3_lock } ;
float adj [ 3 ] = { 0 , endstops . z2_endstop_adj , endstops . z3_endstop_adj } ;
void ( * tempLock ) ( bool ) ;
float tempAdj ;
// manual bubble sort by adjust value
if ( adj [ 1 ] < adj [ 0 ] ) {
tempLock = lock [ 0 ] , tempAdj = adj [ 0 ] ;
lock [ 0 ] = lock [ 1 ] , adj [ 0 ] = adj [ 1 ] ;
lock [ 1 ] = tempLock , adj [ 1 ] = tempAdj ;
}
if ( adj [ 2 ] < adj [ 1 ] ) {
tempLock = lock [ 1 ] , tempAdj = adj [ 1 ] ;
lock [ 1 ] = lock [ 2 ] , adj [ 1 ] = adj [ 2 ] ;
lock [ 2 ] = tempLock , adj [ 2 ] = tempAdj ;
}
if ( adj [ 1 ] < adj [ 0 ] ) {
tempLock = lock [ 0 ] , tempAdj = adj [ 0 ] ;
lock [ 0 ] = lock [ 1 ] , adj [ 0 ] = adj [ 1 ] ;
lock [ 1 ] = tempLock , adj [ 1 ] = tempAdj ;
}
if ( pos_dir ) {
// normalize adj to smallest value and do the first move
( * lock [ 0 ] ) ( true ) ;
do_homing_move ( axis , adj [ 1 ] - adj [ 0 ] ) ;
// lock the second stepper for the final correction
( * lock [ 1 ] ) ( true ) ;
do_homing_move ( axis , adj [ 2 ] - adj [ 1 ] ) ;
}
else {
( * lock [ 2 ] ) ( true ) ;
do_homing_move ( axis , adj [ 1 ] - adj [ 2 ] ) ;
( * lock [ 1 ] ) ( true ) ;
do_homing_move ( axis , adj [ 0 ] - adj [ 1 ] ) ;
}
stepper . set_z_lock ( false ) ;
stepper . set_z2_lock ( false ) ;
stepper . set_z3_lock ( false ) ;
}
# endif
2018-11-09 21:58:04 +01:00
// Reset flags for X, Y, Z motor locking
switch ( axis ) {
# if ENABLED(X_DUAL_ENDSTOPS)
case X_AXIS :
# endif
# if ENABLED(Y_DUAL_ENDSTOPS)
case Y_AXIS :
# endif
# if Z_MULTI_ENDSTOPS
case Z_AXIS :
# endif
stepper . set_separate_multi_axis ( false ) ;
default : break ;
}
2017-09-08 22:35:25 +02:00
# endif
# if IS_SCARA
set_axis_is_at_home ( axis ) ;
2018-09-17 04:24:15 +02:00
sync_plan_position ( ) ;
2017-09-08 22:35:25 +02:00
# elif ENABLED(DELTA)
// Delta has already moved all three towers up in G28
// so here it re-homes each tower in turn.
// Delta homing treats the axes as normal linear axes.
2018-04-12 04:14:48 +02:00
// retrace by the amount specified in delta_endstop_adj + additional dist in order to have minimum steps
2017-09-08 22:35:25 +02:00
if ( delta_endstop_adj [ axis ] * Z_HOME_DIR < = 0 ) {
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPGM ( " delta_endstop_adj: " ) ;
2018-04-13 03:22:29 +02:00
do_homing_move ( axis , delta_endstop_adj [ axis ] - ( MIN_STEPS_PER_SEGMENT + 1 ) * planner . steps_to_mm [ axis ] * Z_HOME_DIR ) ;
2017-09-08 22:35:25 +02:00
}
2018-11-30 21:25:43 +01:00
# else // CARTESIAN / CORE
2017-09-08 22:35:25 +02:00
set_axis_is_at_home ( axis ) ;
sync_plan_position ( ) ;
destination [ axis ] = current_position [ axis ] ;
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_POS ( " > AFTER set_axis_is_at_home " , current_position ) ;
2017-09-08 22:35:25 +02:00
# endif
2018-07-28 01:30:08 +02:00
// Put away the Z probe
# if HOMING_Z_WITH_PROBE
2018-07-29 02:30:14 +02:00
if ( axis = = Z_AXIS & & STOW_PROBE ( ) ) return ;
2018-07-28 01:30:08 +02:00
# endif
2019-08-01 03:50:23 +02:00
# ifdef HOMING_BACKOFF_MM
constexpr float endstop_backoff [ XYZ ] = HOMING_BACKOFF_MM ;
2019-08-02 03:32:45 +02:00
const float backoff_mm = endstop_backoff [
2019-08-01 03:50:23 +02:00
# if ENABLED(DELTA)
Z_AXIS
# else
axis
# endif
] ;
if ( backoff_mm ) {
current_position [ axis ] - = ABS ( backoff_mm ) * axis_home_dir ;
2019-09-19 00:04:13 +02:00
line_to_current_position (
# if HOMING_Z_WITH_PROBE
( axis = = Z_AXIS ) ? MMM_TO_MMS ( Z_PROBE_SPEED_FAST ) :
# endif
homing_feedrate ( axis )
) ;
2019-08-01 03:50:23 +02:00
}
# endif
2018-06-03 03:39:00 +02:00
// Clear retracted status if homing the Z axis
2018-01-29 20:40:04 +01:00
# if ENABLED(FWRETRACT)
2018-09-09 04:16:41 +02:00
if ( axis = = Z_AXIS ) fwretract . current_hop = 0.0 ;
2018-01-29 20:40:04 +01:00
# endif
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " <<< homeaxis( " , axis_codes [ axis ] , " ) " ) ;
2017-09-08 22:35:25 +02:00
} // homeaxis()
2018-11-03 09:56:33 +01:00
# if HAS_WORKSPACE_OFFSET
void update_workspace_offset ( const AxisEnum axis ) {
workspace_offset [ axis ] = home_offset [ axis ] + position_shift [ axis ] ;
2019-03-14 08:25:42 +01:00
if ( DEBUGGING ( LEVELING ) ) DEBUG_ECHOLNPAIR ( " Axis " , axis_codes [ axis ] , " home_offset = " , home_offset [ axis ] , " position_shift = " , position_shift [ axis ] ) ;
2017-09-08 22:35:25 +02:00
}
2018-11-03 09:56:33 +01:00
# endif
2017-09-08 22:35:25 +02:00
# if HAS_M206_COMMAND
/**
2018-01-22 13:10:35 +01:00
* Change the home offset for an axis .
* Also refreshes the workspace offset .
2017-09-08 22:35:25 +02:00
*/
void set_home_offset ( const AxisEnum axis , const float v ) {
home_offset [ axis ] = v ;
2018-11-03 09:56:33 +01:00
update_workspace_offset ( axis ) ;
2017-09-08 22:35:25 +02:00
}
# endif // HAS_M206_COMMAND