Initial split-up of G-code handlers by category

This commit is contained in:
Scott Lahteine
2017-09-06 06:28:31 -05:00
parent e0a189a481
commit 4231faf779
162 changed files with 12052 additions and 336 deletions

View File

@ -0,0 +1,27 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
void gcode_G26() {
ubl.G26();
}

View File

@ -0,0 +1,324 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#include "common.h"
#if HOTENDS > 1
#include "../control/tool_change.h"
#endif
#if ENABLED(QUICK_HOME)
static void quick_home_xy() {
// Pretend the current position is 0,0
current_position[X_AXIS] = current_position[Y_AXIS] = 0.0;
sync_plan_position();
const int x_axis_home_dir =
#if ENABLED(DUAL_X_CARRIAGE)
x_home_dir(active_extruder)
#else
home_dir(X_AXIS)
#endif
;
const float mlx = max_length(X_AXIS),
mly = max_length(Y_AXIS),
mlratio = mlx > mly ? mly / mlx : mlx / mly,
fr_mm_s = min(homing_feedrate(X_AXIS), homing_feedrate(Y_AXIS)) * SQRT(sq(mlratio) + 1.0);
do_blocking_move_to_xy(1.5 * mlx * x_axis_home_dir, 1.5 * mly * home_dir(Y_AXIS), fr_mm_s);
endstops.hit_on_purpose(); // clear endstop hit flags
current_position[X_AXIS] = current_position[Y_AXIS] = 0.0;
}
#endif // QUICK_HOME
#if ENABLED(Z_SAFE_HOMING)
inline void home_z_safely() {
// Disallow Z homing if X or Y are unknown
if (!axis_known_position[X_AXIS] || !axis_known_position[Y_AXIS]) {
LCD_MESSAGEPGM(MSG_ERR_Z_HOMING);
SERIAL_ECHO_START();
SERIAL_ECHOLNPGM(MSG_ERR_Z_HOMING);
return;
}
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Z_SAFE_HOMING >>>");
#endif
SYNC_PLAN_POSITION_KINEMATIC();
/**
* Move the Z probe (or just the nozzle) to the safe homing point
*/
destination[X_AXIS] = LOGICAL_X_POSITION(Z_SAFE_HOMING_X_POINT);
destination[Y_AXIS] = LOGICAL_Y_POSITION(Z_SAFE_HOMING_Y_POINT);
destination[Z_AXIS] = current_position[Z_AXIS]; // Z is already at the right height
#if HOMING_Z_WITH_PROBE
destination[X_AXIS] -= X_PROBE_OFFSET_FROM_EXTRUDER;
destination[Y_AXIS] -= Y_PROBE_OFFSET_FROM_EXTRUDER;
#endif
if (position_is_reachable_xy(destination[X_AXIS], destination[Y_AXIS])) {
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("Z_SAFE_HOMING", destination);
#endif
// This causes the carriage on Dual X to unpark
#if ENABLED(DUAL_X_CARRIAGE)
active_extruder_parked = false;
#endif
do_blocking_move_to_xy(destination[X_AXIS], destination[Y_AXIS]);
HOMEAXIS(Z);
}
else {
LCD_MESSAGEPGM(MSG_ZPROBE_OUT);
SERIAL_ECHO_START();
SERIAL_ECHOLNPGM(MSG_ZPROBE_OUT);
}
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< Z_SAFE_HOMING");
#endif
}
#endif // Z_SAFE_HOMING
/**
* G28: Home all axes according to settings
*
* Parameters
*
* None Home to all axes with no parameters.
* With QUICK_HOME enabled XY will home together, then Z.
*
* Cartesian parameters
*
* X Home to the X endstop
* Y Home to the Y endstop
* Z Home to the Z endstop
*
*/
void gcode_G28(const bool always_home_all) {
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) {
SERIAL_ECHOLNPGM(">>> gcode_G28");
log_machine_info();
}
#endif
// Wait for planner moves to finish!
stepper.synchronize();
// Cancel the active G29 session
#if ENABLED(PROBE_MANUALLY)
g29_in_progress = false;
#endif
// Disable the leveling matrix before homing
#if HAS_LEVELING
#if ENABLED(AUTO_BED_LEVELING_UBL)
const bool ubl_state_at_entry = leveling_is_active();
#endif
set_bed_leveling_enabled(false);
#endif
#if ENABLED(CNC_WORKSPACE_PLANES)
workspace_plane = PLANE_XY;
#endif
// Always home with tool 0 active
#if HOTENDS > 1
const uint8_t old_tool_index = active_extruder;
tool_change(0, 0, true);
#endif
#if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE)
extruder_duplication_enabled = false;
#endif
setup_for_endstop_or_probe_move();
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("> endstops.enable(true)");
#endif
endstops.enable(true); // Enable endstops for next homing move
#if ENABLED(DELTA)
home_delta();
UNUSED(always_home_all);
#else // NOT DELTA
const bool homeX = always_home_all || parser.seen('X'),
homeY = always_home_all || parser.seen('Y'),
homeZ = always_home_all || parser.seen('Z'),
home_all = (!homeX && !homeY && !homeZ) || (homeX && homeY && homeZ);
set_destination_to_current();
#if Z_HOME_DIR > 0 // If homing away from BED do Z first
if (home_all || homeZ) {
HOMEAXIS(Z);
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("> HOMEAXIS(Z)", current_position);
#endif
}
#else
if (home_all || homeX || homeY) {
// Raise Z before homing any other axes and z is not already high enough (never lower z)
destination[Z_AXIS] = LOGICAL_Z_POSITION(Z_HOMING_HEIGHT);
if (destination[Z_AXIS] > current_position[Z_AXIS]) {
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING))
SERIAL_ECHOLNPAIR("Raise Z (before homing) to ", destination[Z_AXIS]);
#endif
do_blocking_move_to_z(destination[Z_AXIS]);
}
}
#endif
#if ENABLED(QUICK_HOME)
if (home_all || (homeX && homeY)) quick_home_xy();
#endif
#if ENABLED(HOME_Y_BEFORE_X)
// Home Y
if (home_all || homeY) {
HOMEAXIS(Y);
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("> homeY", current_position);
#endif
}
#endif
// Home X
if (home_all || homeX) {
#if ENABLED(DUAL_X_CARRIAGE)
// Always home the 2nd (right) extruder first
active_extruder = 1;
HOMEAXIS(X);
// Remember this extruder's position for later tool change
inactive_extruder_x_pos = RAW_X_POSITION(current_position[X_AXIS]);
// Home the 1st (left) extruder
active_extruder = 0;
HOMEAXIS(X);
// Consider the active extruder to be parked
COPY(raised_parked_position, current_position);
delayed_move_time = 0;
active_extruder_parked = true;
#else
HOMEAXIS(X);
#endif
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("> homeX", current_position);
#endif
}
#if DISABLED(HOME_Y_BEFORE_X)
// Home Y
if (home_all || homeY) {
HOMEAXIS(Y);
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("> homeY", current_position);
#endif
}
#endif
// Home Z last if homing towards the bed
#if Z_HOME_DIR < 0
if (home_all || homeZ) {
#if ENABLED(Z_SAFE_HOMING)
home_z_safely();
#else
HOMEAXIS(Z);
#endif
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("> (home_all || homeZ) > final", current_position);
#endif
} // home_all || homeZ
#endif // Z_HOME_DIR < 0
SYNC_PLAN_POSITION_KINEMATIC();
#endif // !DELTA (gcode_G28)
endstops.not_homing();
#if ENABLED(DELTA) && ENABLED(DELTA_HOME_TO_SAFE_ZONE)
// move to a height where we can use the full xy-area
do_blocking_move_to_z(delta_clip_start_height);
#endif
#if ENABLED(AUTO_BED_LEVELING_UBL)
set_bed_leveling_enabled(ubl_state_at_entry);
#endif
clean_up_after_endstop_or_probe_move();
// Restore the active tool after homing
#if HOTENDS > 1
tool_change(old_tool_index, 0,
#if ENABLED(PARKING_EXTRUDER)
false // fetch the previous toolhead
#else
true
#endif
);
#endif
lcd_refresh();
report_current_position();
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< gcode_G28");
#endif
}

View File

@ -0,0 +1,946 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#if ABL_GRID
#if ENABLED(PROBE_Y_FIRST)
#define PR_OUTER_VAR xCount
#define PR_OUTER_END abl_grid_points_x
#define PR_INNER_VAR yCount
#define PR_INNER_END abl_grid_points_y
#else
#define PR_OUTER_VAR yCount
#define PR_OUTER_END abl_grid_points_y
#define PR_INNER_VAR xCount
#define PR_INNER_END abl_grid_points_x
#endif
#endif
/**
* G29: Detailed Z probe, probes the bed at 3 or more points.
* Will fail if the printer has not been homed with G28.
*
* Enhanced G29 Auto Bed Leveling Probe Routine
*
* D Dry-Run mode. Just evaluate the bed Topology - Don't apply
* or alter the bed level data. Useful to check the topology
* after a first run of G29.
*
* J Jettison current bed leveling data
*
* V Set the verbose level (0-4). Example: "G29 V3"
*
* Parameters With LINEAR leveling only:
*
* P Set the size of the grid that will be probed (P x P points).
* Example: "G29 P4"
*
* X Set the X size of the grid that will be probed (X x Y points).
* Example: "G29 X7 Y5"
*
* Y Set the Y size of the grid that will be probed (X x Y points).
*
* T Generate a Bed Topology Report. Example: "G29 P5 T" for a detailed report.
* This is useful for manual bed leveling and finding flaws in the bed (to
* assist with part placement).
* Not supported by non-linear delta printer bed leveling.
*
* Parameters With LINEAR and BILINEAR leveling only:
*
* S Set the XY travel speed between probe points (in units/min)
*
* F Set the Front limit of the probing grid
* B Set the Back limit of the probing grid
* L Set the Left limit of the probing grid
* R Set the Right limit of the probing grid
*
* Parameters with DEBUG_LEVELING_FEATURE only:
*
* C Make a totally fake grid with no actual probing.
* For use in testing when no probing is possible.
*
* Parameters with BILINEAR leveling only:
*
* Z Supply an additional Z probe offset
*
* Extra parameters with PROBE_MANUALLY:
*
* To do manual probing simply repeat G29 until the procedure is complete.
* The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort.
*
* Q Query leveling and G29 state
*
* A Abort current leveling procedure
*
* Extra parameters with BILINEAR only:
*
* W Write a mesh point. (If G29 is idle.)
* I X index for mesh point
* J Y index for mesh point
* X X for mesh point, overrides I
* Y Y for mesh point, overrides J
* Z Z for mesh point. Otherwise, raw current Z.
*
* Without PROBE_MANUALLY:
*
* E By default G29 will engage the Z probe, test the bed, then disengage.
* Include "E" to engage/disengage the Z probe for each sample.
* There's no extra effect if you have a fixed Z probe.
*
*/
void gcode_G29() {
// G29 Q is also available if debugging
#if ENABLED(DEBUG_LEVELING_FEATURE)
const bool query = parser.seen('Q');
const uint8_t old_debug_flags = marlin_debug_flags;
if (query) marlin_debug_flags |= DEBUG_LEVELING;
if (DEBUGGING(LEVELING)) {
DEBUG_POS(">>> gcode_G29", current_position);
log_machine_info();
}
marlin_debug_flags = old_debug_flags;
#if DISABLED(PROBE_MANUALLY)
if (query) return;
#endif
#endif
#if ENABLED(PROBE_MANUALLY)
const bool seenA = parser.seen('A'), seenQ = parser.seen('Q'), no_action = seenA || seenQ;
#endif
#if ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY)
const bool faux = parser.boolval('C');
#elif ENABLED(PROBE_MANUALLY)
const bool faux = no_action;
#else
bool constexpr faux = false;
#endif
// Don't allow auto-leveling without homing first
if (axis_unhomed_error()) return;
// Define local vars 'static' for manual probing, 'auto' otherwise
#if ENABLED(PROBE_MANUALLY)
#define ABL_VAR static
#else
#define ABL_VAR
#endif
ABL_VAR int verbose_level;
ABL_VAR float xProbe, yProbe, measured_z;
ABL_VAR bool dryrun, abl_should_enable;
#if ENABLED(PROBE_MANUALLY) || ENABLED(AUTO_BED_LEVELING_LINEAR)
ABL_VAR int abl_probe_index;
#endif
#if HAS_SOFTWARE_ENDSTOPS && ENABLED(PROBE_MANUALLY)
ABL_VAR bool enable_soft_endstops = true;
#endif
#if ABL_GRID
#if ENABLED(PROBE_MANUALLY)
ABL_VAR uint8_t PR_OUTER_VAR;
ABL_VAR int8_t PR_INNER_VAR;
#endif
ABL_VAR int left_probe_bed_position, right_probe_bed_position, front_probe_bed_position, back_probe_bed_position;
ABL_VAR float xGridSpacing = 0, yGridSpacing = 0;
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
ABL_VAR uint8_t abl_grid_points_x = GRID_MAX_POINTS_X,
abl_grid_points_y = GRID_MAX_POINTS_Y;
ABL_VAR bool do_topography_map;
#else // Bilinear
uint8_t constexpr abl_grid_points_x = GRID_MAX_POINTS_X,
abl_grid_points_y = GRID_MAX_POINTS_Y;
#endif
#if ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(PROBE_MANUALLY)
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
ABL_VAR int abl2;
#else // Bilinear
int constexpr abl2 = GRID_MAX_POINTS;
#endif
#endif
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
ABL_VAR float zoffset;
#elif ENABLED(AUTO_BED_LEVELING_LINEAR)
ABL_VAR int indexIntoAB[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y];
ABL_VAR float eqnAMatrix[GRID_MAX_POINTS * 3], // "A" matrix of the linear system of equations
eqnBVector[GRID_MAX_POINTS], // "B" vector of Z points
mean;
#endif
#elif ENABLED(AUTO_BED_LEVELING_3POINT)
int constexpr abl2 = 3;
// Probe at 3 arbitrary points
ABL_VAR vector_3 points[3] = {
vector_3(ABL_PROBE_PT_1_X, ABL_PROBE_PT_1_Y, 0),
vector_3(ABL_PROBE_PT_2_X, ABL_PROBE_PT_2_Y, 0),
vector_3(ABL_PROBE_PT_3_X, ABL_PROBE_PT_3_Y, 0)
};
#endif // AUTO_BED_LEVELING_3POINT
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
struct linear_fit_data lsf_results;
incremental_LSF_reset(&lsf_results);
#endif
/**
* On the initial G29 fetch command parameters.
*/
if (!g29_in_progress) {
#if ENABLED(PROBE_MANUALLY) || ENABLED(AUTO_BED_LEVELING_LINEAR)
abl_probe_index = -1;
#endif
abl_should_enable = leveling_is_active();
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
if (parser.seen('W')) {
if (!leveling_is_valid()) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM("No bilinear grid");
return;
}
const float z = parser.floatval('Z', RAW_CURRENT_POSITION(Z));
if (!WITHIN(z, -10, 10)) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM("Bad Z value");
return;
}
const float x = parser.floatval('X', NAN),
y = parser.floatval('Y', NAN);
int8_t i = parser.byteval('I', -1),
j = parser.byteval('J', -1);
if (!isnan(x) && !isnan(y)) {
// Get nearest i / j from x / y
i = (x - LOGICAL_X_POSITION(bilinear_start[X_AXIS]) + 0.5 * xGridSpacing) / xGridSpacing;
j = (y - LOGICAL_Y_POSITION(bilinear_start[Y_AXIS]) + 0.5 * yGridSpacing) / yGridSpacing;
i = constrain(i, 0, GRID_MAX_POINTS_X - 1);
j = constrain(j, 0, GRID_MAX_POINTS_Y - 1);
}
if (WITHIN(i, 0, GRID_MAX_POINTS_X - 1) && WITHIN(j, 0, GRID_MAX_POINTS_Y)) {
set_bed_leveling_enabled(false);
z_values[i][j] = z;
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
bed_level_virt_interpolate();
#endif
set_bed_leveling_enabled(abl_should_enable);
}
return;
} // parser.seen('W')
#endif
#if HAS_LEVELING
// Jettison bed leveling data
if (parser.seen('J')) {
reset_bed_level();
return;
}
#endif
verbose_level = parser.intval('V');
if (!WITHIN(verbose_level, 0, 4)) {
SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-4).");
return;
}
dryrun = parser.boolval('D')
#if ENABLED(PROBE_MANUALLY)
|| no_action
#endif
;
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
do_topography_map = verbose_level > 2 || parser.boolval('T');
// X and Y specify points in each direction, overriding the default
// These values may be saved with the completed mesh
abl_grid_points_x = parser.intval('X', GRID_MAX_POINTS_X);
abl_grid_points_y = parser.intval('Y', GRID_MAX_POINTS_Y);
if (parser.seenval('P')) abl_grid_points_x = abl_grid_points_y = parser.value_int();
if (abl_grid_points_x < 2 || abl_grid_points_y < 2) {
SERIAL_PROTOCOLLNPGM("?Number of probe points is implausible (2 minimum).");
return;
}
abl2 = abl_grid_points_x * abl_grid_points_y;
mean = 0;
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
zoffset = parser.linearval('Z');
#endif
#if ABL_GRID
xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_SPEED));
left_probe_bed_position = (int)parser.linearval('L', LOGICAL_X_POSITION(LEFT_PROBE_BED_POSITION));
right_probe_bed_position = (int)parser.linearval('R', LOGICAL_X_POSITION(RIGHT_PROBE_BED_POSITION));
front_probe_bed_position = (int)parser.linearval('F', LOGICAL_Y_POSITION(FRONT_PROBE_BED_POSITION));
back_probe_bed_position = (int)parser.linearval('B', LOGICAL_Y_POSITION(BACK_PROBE_BED_POSITION));
const bool left_out_l = left_probe_bed_position < LOGICAL_X_POSITION(MIN_PROBE_X),
left_out = left_out_l || left_probe_bed_position > right_probe_bed_position - (MIN_PROBE_EDGE),
right_out_r = right_probe_bed_position > LOGICAL_X_POSITION(MAX_PROBE_X),
right_out = right_out_r || right_probe_bed_position < left_probe_bed_position + MIN_PROBE_EDGE,
front_out_f = front_probe_bed_position < LOGICAL_Y_POSITION(MIN_PROBE_Y),
front_out = front_out_f || front_probe_bed_position > back_probe_bed_position - (MIN_PROBE_EDGE),
back_out_b = back_probe_bed_position > LOGICAL_Y_POSITION(MAX_PROBE_Y),
back_out = back_out_b || back_probe_bed_position < front_probe_bed_position + MIN_PROBE_EDGE;
if (left_out || right_out || front_out || back_out) {
if (left_out) {
out_of_range_error(PSTR("(L)eft"));
left_probe_bed_position = left_out_l ? LOGICAL_X_POSITION(MIN_PROBE_X) : right_probe_bed_position - (MIN_PROBE_EDGE);
}
if (right_out) {
out_of_range_error(PSTR("(R)ight"));
right_probe_bed_position = right_out_r ? LOGICAL_Y_POSITION(MAX_PROBE_X) : left_probe_bed_position + MIN_PROBE_EDGE;
}
if (front_out) {
out_of_range_error(PSTR("(F)ront"));
front_probe_bed_position = front_out_f ? LOGICAL_Y_POSITION(MIN_PROBE_Y) : back_probe_bed_position - (MIN_PROBE_EDGE);
}
if (back_out) {
out_of_range_error(PSTR("(B)ack"));
back_probe_bed_position = back_out_b ? LOGICAL_Y_POSITION(MAX_PROBE_Y) : front_probe_bed_position + MIN_PROBE_EDGE;
}
return;
}
// probe at the points of a lattice grid
xGridSpacing = (right_probe_bed_position - left_probe_bed_position) / (abl_grid_points_x - 1);
yGridSpacing = (back_probe_bed_position - front_probe_bed_position) / (abl_grid_points_y - 1);
#endif // ABL_GRID
if (verbose_level > 0) {
SERIAL_PROTOCOLLNPGM("G29 Auto Bed Leveling");
if (dryrun) SERIAL_PROTOCOLLNPGM("Running in DRY-RUN mode");
}
stepper.synchronize();
// Disable auto bed leveling during G29
planner.abl_enabled = false;
if (!dryrun) {
// Re-orient the current position without leveling
// based on where the steppers are positioned.
set_current_from_steppers_for_axis(ALL_AXES);
// Sync the planner to where the steppers stopped
SYNC_PLAN_POSITION_KINEMATIC();
}
#if HAS_BED_PROBE
// Deploy the probe. Probe will raise if needed.
if (DEPLOY_PROBE()) {
planner.abl_enabled = abl_should_enable;
return;
}
#endif
if (!faux) setup_for_endstop_or_probe_move();
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
#if ENABLED(PROBE_MANUALLY)
if (!no_action)
#endif
if ( xGridSpacing != bilinear_grid_spacing[X_AXIS]
|| yGridSpacing != bilinear_grid_spacing[Y_AXIS]
|| left_probe_bed_position != LOGICAL_X_POSITION(bilinear_start[X_AXIS])
|| front_probe_bed_position != LOGICAL_Y_POSITION(bilinear_start[Y_AXIS])
) {
if (dryrun) {
// Before reset bed level, re-enable to correct the position
planner.abl_enabled = abl_should_enable;
}
// Reset grid to 0.0 or "not probed". (Also disables ABL)
reset_bed_level();
// Initialize a grid with the given dimensions
bilinear_grid_spacing[X_AXIS] = xGridSpacing;
bilinear_grid_spacing[Y_AXIS] = yGridSpacing;
bilinear_start[X_AXIS] = RAW_X_POSITION(left_probe_bed_position);
bilinear_start[Y_AXIS] = RAW_Y_POSITION(front_probe_bed_position);
// Can't re-enable (on error) until the new grid is written
abl_should_enable = false;
}
#endif // AUTO_BED_LEVELING_BILINEAR
#if ENABLED(AUTO_BED_LEVELING_3POINT)
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("> 3-point Leveling");
#endif
// Probe at 3 arbitrary points
points[0].z = points[1].z = points[2].z = 0;
#endif // AUTO_BED_LEVELING_3POINT
} // !g29_in_progress
#if ENABLED(PROBE_MANUALLY)
// For manual probing, get the next index to probe now.
// On the first probe this will be incremented to 0.
if (!no_action) {
++abl_probe_index;
g29_in_progress = true;
}
// Abort current G29 procedure, go back to idle state
if (seenA && g29_in_progress) {
SERIAL_PROTOCOLLNPGM("Manual G29 aborted");
#if HAS_SOFTWARE_ENDSTOPS
soft_endstops_enabled = enable_soft_endstops;
#endif
planner.abl_enabled = abl_should_enable;
g29_in_progress = false;
#if ENABLED(LCD_BED_LEVELING)
lcd_wait_for_move = false;
#endif
}
// Query G29 status
if (verbose_level || seenQ) {
SERIAL_PROTOCOLPGM("Manual G29 ");
if (g29_in_progress) {
SERIAL_PROTOCOLPAIR("point ", min(abl_probe_index + 1, abl2));
SERIAL_PROTOCOLLNPAIR(" of ", abl2);
}
else
SERIAL_PROTOCOLLNPGM("idle");
}
if (no_action) return;
if (abl_probe_index == 0) {
// For the initial G29 save software endstop state
#if HAS_SOFTWARE_ENDSTOPS
enable_soft_endstops = soft_endstops_enabled;
#endif
}
else {
// For G29 after adjusting Z.
// Save the previous Z before going to the next point
measured_z = current_position[Z_AXIS];
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
mean += measured_z;
eqnBVector[abl_probe_index] = measured_z;
eqnAMatrix[abl_probe_index + 0 * abl2] = xProbe;
eqnAMatrix[abl_probe_index + 1 * abl2] = yProbe;
eqnAMatrix[abl_probe_index + 2 * abl2] = 1;
incremental_LSF(&lsf_results, xProbe, yProbe, measured_z);
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
z_values[xCount][yCount] = measured_z + zoffset;
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) {
SERIAL_PROTOCOLPAIR("Save X", xCount);
SERIAL_PROTOCOLPAIR(" Y", yCount);
SERIAL_PROTOCOLLNPAIR(" Z", measured_z + zoffset);
}
#endif
#elif ENABLED(AUTO_BED_LEVELING_3POINT)
points[abl_probe_index].z = measured_z;
#endif
}
//
// If there's another point to sample, move there with optional lift.
//
#if ABL_GRID
// Skip any unreachable points
while (abl_probe_index < abl2) {
// Set xCount, yCount based on abl_probe_index, with zig-zag
PR_OUTER_VAR = abl_probe_index / PR_INNER_END;
PR_INNER_VAR = abl_probe_index - (PR_OUTER_VAR * PR_INNER_END);
// Probe in reverse order for every other row/column
bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_END) & 1);
if (zig) PR_INNER_VAR = (PR_INNER_END - 1) - PR_INNER_VAR;
const float xBase = xCount * xGridSpacing + left_probe_bed_position,
yBase = yCount * yGridSpacing + front_probe_bed_position;
xProbe = FLOOR(xBase + (xBase < 0 ? 0 : 0.5));
yProbe = FLOOR(yBase + (yBase < 0 ? 0 : 0.5));
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
indexIntoAB[xCount][yCount] = abl_probe_index;
#endif
// Keep looping till a reachable point is found
if (position_is_reachable_xy(xProbe, yProbe)) break;
++abl_probe_index;
}
// Is there a next point to move to?
if (abl_probe_index < abl2) {
_manual_goto_xy(xProbe, yProbe); // Can be used here too!
#if HAS_SOFTWARE_ENDSTOPS
// Disable software endstops to allow manual adjustment
// If G29 is not completed, they will not be re-enabled
soft_endstops_enabled = false;
#endif
return;
}
else {
// Leveling done! Fall through to G29 finishing code below
SERIAL_PROTOCOLLNPGM("Grid probing done.");
// Re-enable software endstops, if needed
#if HAS_SOFTWARE_ENDSTOPS
soft_endstops_enabled = enable_soft_endstops;
#endif
}
#elif ENABLED(AUTO_BED_LEVELING_3POINT)
// Probe at 3 arbitrary points
if (abl_probe_index < 3) {
xProbe = LOGICAL_X_POSITION(points[abl_probe_index].x);
yProbe = LOGICAL_Y_POSITION(points[abl_probe_index].y);
#if HAS_SOFTWARE_ENDSTOPS
// Disable software endstops to allow manual adjustment
// If G29 is not completed, they will not be re-enabled
soft_endstops_enabled = false;
#endif
return;
}
else {
SERIAL_PROTOCOLLNPGM("3-point probing done.");
// Re-enable software endstops, if needed
#if HAS_SOFTWARE_ENDSTOPS
soft_endstops_enabled = enable_soft_endstops;
#endif
if (!dryrun) {
vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal();
if (planeNormal.z < 0) {
planeNormal.x *= -1;
planeNormal.y *= -1;
planeNormal.z *= -1;
}
planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal);
// Can't re-enable (on error) until the new grid is written
abl_should_enable = false;
}
}
#endif // AUTO_BED_LEVELING_3POINT
#else // !PROBE_MANUALLY
{
const bool stow_probe_after_each = parser.boolval('E');
measured_z = 0;
#if ABL_GRID
bool zig = PR_OUTER_END & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION
// Outer loop is Y with PROBE_Y_FIRST disabled
for (uint8_t PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_END && !isnan(measured_z); PR_OUTER_VAR++) {
int8_t inStart, inStop, inInc;
if (zig) { // away from origin
inStart = 0;
inStop = PR_INNER_END;
inInc = 1;
}
else { // towards origin
inStart = PR_INNER_END - 1;
inStop = -1;
inInc = -1;
}
zig ^= true; // zag
// Inner loop is Y with PROBE_Y_FIRST enabled
for (int8_t PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc) {
float xBase = left_probe_bed_position + xGridSpacing * xCount,
yBase = front_probe_bed_position + yGridSpacing * yCount;
xProbe = FLOOR(xBase + (xBase < 0 ? 0 : 0.5));
yProbe = FLOOR(yBase + (yBase < 0 ? 0 : 0.5));
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
indexIntoAB[xCount][yCount] = ++abl_probe_index; // 0...
#endif
#if IS_KINEMATIC
// Avoid probing outside the round or hexagonal area
if (!position_is_reachable_by_probe_xy(xProbe, yProbe)) continue;
#endif
measured_z = faux ? 0.001 * random(-100, 101) : probe_pt(xProbe, yProbe, stow_probe_after_each, verbose_level);
if (isnan(measured_z)) {
planner.abl_enabled = abl_should_enable;
break;
}
#if ENABLED(AUTO_BED_LEVELING_LINEAR)
mean += measured_z;
eqnBVector[abl_probe_index] = measured_z;
eqnAMatrix[abl_probe_index + 0 * abl2] = xProbe;
eqnAMatrix[abl_probe_index + 1 * abl2] = yProbe;
eqnAMatrix[abl_probe_index + 2 * abl2] = 1;
incremental_LSF(&lsf_results, xProbe, yProbe, measured_z);
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
z_values[xCount][yCount] = measured_z + zoffset;
#endif
abl_should_enable = false;
idle();
} // inner
} // outer
#elif ENABLED(AUTO_BED_LEVELING_3POINT)
// Probe at 3 arbitrary points
for (uint8_t i = 0; i < 3; ++i) {
// Retain the last probe position
xProbe = LOGICAL_X_POSITION(points[i].x);
yProbe = LOGICAL_Y_POSITION(points[i].y);
measured_z = faux ? 0.001 * random(-100, 101) : probe_pt(xProbe, yProbe, stow_probe_after_each, verbose_level);
if (isnan(measured_z)) {
planner.abl_enabled = abl_should_enable;
break;
}
points[i].z = measured_z;
}
if (!dryrun && !isnan(measured_z)) {
vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal();
if (planeNormal.z < 0) {
planeNormal.x *= -1;
planeNormal.y *= -1;
planeNormal.z *= -1;
}
planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal);
// Can't re-enable (on error) until the new grid is written
abl_should_enable = false;
}
#endif // AUTO_BED_LEVELING_3POINT
// Raise to _Z_CLEARANCE_DEPLOY_PROBE. Stow the probe.
if (STOW_PROBE()) {
planner.abl_enabled = abl_should_enable;
measured_z = NAN;
}
}
#endif // !PROBE_MANUALLY
//
// G29 Finishing Code
//
// Unless this is a dry run, auto bed leveling will
// definitely be enabled after this point.
//
// If code above wants to continue leveling, it should
// return or loop before this point.
//
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position);
#endif
#if ENABLED(PROBE_MANUALLY)
g29_in_progress = false;
#if ENABLED(LCD_BED_LEVELING)
lcd_wait_for_move = false;
#endif
#endif
// Calculate leveling, print reports, correct the position
if (!isnan(measured_z)) {
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
if (!dryrun) extrapolate_unprobed_bed_level();
print_bilinear_leveling_grid();
refresh_bed_level();
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
print_bilinear_leveling_grid_virt();
#endif
#elif ENABLED(AUTO_BED_LEVELING_LINEAR)
// For LINEAR leveling calculate matrix, print reports, correct the position
/**
* solve the plane equation ax + by + d = z
* A is the matrix with rows [x y 1] for all the probed points
* B is the vector of the Z positions
* the normal vector to the plane is formed by the coefficients of the
* plane equation in the standard form, which is Vx*x+Vy*y+Vz*z+d = 0
* so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z
*/
float plane_equation_coefficients[3];
finish_incremental_LSF(&lsf_results);
plane_equation_coefficients[0] = -lsf_results.A; // We should be able to eliminate the '-' on these three lines and down below
plane_equation_coefficients[1] = -lsf_results.B; // but that is not yet tested.
plane_equation_coefficients[2] = -lsf_results.D;
mean /= abl2;
if (verbose_level) {
SERIAL_PROTOCOLPGM("Eqn coefficients: a: ");
SERIAL_PROTOCOL_F(plane_equation_coefficients[0], 8);
SERIAL_PROTOCOLPGM(" b: ");
SERIAL_PROTOCOL_F(plane_equation_coefficients[1], 8);
SERIAL_PROTOCOLPGM(" d: ");
SERIAL_PROTOCOL_F(plane_equation_coefficients[2], 8);
SERIAL_EOL();
if (verbose_level > 2) {
SERIAL_PROTOCOLPGM("Mean of sampled points: ");
SERIAL_PROTOCOL_F(mean, 8);
SERIAL_EOL();
}
}
// Create the matrix but don't correct the position yet
if (!dryrun)
planner.bed_level_matrix = matrix_3x3::create_look_at(
vector_3(-plane_equation_coefficients[0], -plane_equation_coefficients[1], 1) // We can eliminate the '-' here and up above
);
// Show the Topography map if enabled
if (do_topography_map) {
SERIAL_PROTOCOLLNPGM("\nBed Height Topography:\n"
" +--- BACK --+\n"
" | |\n"
" L | (+) | R\n"
" E | | I\n"
" F | (-) N (+) | G\n"
" T | | H\n"
" | (-) | T\n"
" | |\n"
" O-- FRONT --+\n"
" (0,0)");
float min_diff = 999;
for (int8_t yy = abl_grid_points_y - 1; yy >= 0; yy--) {
for (uint8_t xx = 0; xx < abl_grid_points_x; xx++) {
int ind = indexIntoAB[xx][yy];
float diff = eqnBVector[ind] - mean,
x_tmp = eqnAMatrix[ind + 0 * abl2],
y_tmp = eqnAMatrix[ind + 1 * abl2],
z_tmp = 0;
apply_rotation_xyz(planner.bed_level_matrix, x_tmp, y_tmp, z_tmp);
NOMORE(min_diff, eqnBVector[ind] - z_tmp);
if (diff >= 0.0)
SERIAL_PROTOCOLPGM(" +"); // Include + for column alignment
else
SERIAL_PROTOCOLCHAR(' ');
SERIAL_PROTOCOL_F(diff, 5);
} // xx
SERIAL_EOL();
} // yy
SERIAL_EOL();
if (verbose_level > 3) {
SERIAL_PROTOCOLLNPGM("\nCorrected Bed Height vs. Bed Topology:");
for (int8_t yy = abl_grid_points_y - 1; yy >= 0; yy--) {
for (uint8_t xx = 0; xx < abl_grid_points_x; xx++) {
int ind = indexIntoAB[xx][yy];
float x_tmp = eqnAMatrix[ind + 0 * abl2],
y_tmp = eqnAMatrix[ind + 1 * abl2],
z_tmp = 0;
apply_rotation_xyz(planner.bed_level_matrix, x_tmp, y_tmp, z_tmp);
float diff = eqnBVector[ind] - z_tmp - min_diff;
if (diff >= 0.0)
SERIAL_PROTOCOLPGM(" +");
// Include + for column alignment
else
SERIAL_PROTOCOLCHAR(' ');
SERIAL_PROTOCOL_F(diff, 5);
} // xx
SERIAL_EOL();
} // yy
SERIAL_EOL();
}
} //do_topography_map
#endif // AUTO_BED_LEVELING_LINEAR
#if ABL_PLANAR
// For LINEAR and 3POINT leveling correct the current position
if (verbose_level > 0)
planner.bed_level_matrix.debug(PSTR("\n\nBed Level Correction Matrix:"));
if (!dryrun) {
//
// Correct the current XYZ position based on the tilted plane.
//
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position);
#endif
float converted[XYZ];
COPY(converted, current_position);
planner.abl_enabled = true;
planner.unapply_leveling(converted); // use conversion machinery
planner.abl_enabled = false;
// Use the last measured distance to the bed, if possible
if ( NEAR(current_position[X_AXIS], xProbe - (X_PROBE_OFFSET_FROM_EXTRUDER))
&& NEAR(current_position[Y_AXIS], yProbe - (Y_PROBE_OFFSET_FROM_EXTRUDER))
) {
const float simple_z = current_position[Z_AXIS] - measured_z;
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) {
SERIAL_ECHOPAIR("Z from Probe:", simple_z);
SERIAL_ECHOPAIR(" Matrix:", converted[Z_AXIS]);
SERIAL_ECHOLNPAIR(" Discrepancy:", simple_z - converted[Z_AXIS]);
}
#endif
converted[Z_AXIS] = simple_z;
}
// The rotated XY and corrected Z are now current_position
COPY(current_position, converted);
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position);
#endif
}
#elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
if (!dryrun) {
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("G29 uncorrected Z:", current_position[Z_AXIS]);
#endif
// Unapply the offset because it is going to be immediately applied
// and cause compensation movement in Z
current_position[Z_AXIS] -= bilinear_z_offset(current_position);
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR(" corrected Z:", current_position[Z_AXIS]);
#endif
}
#endif // ABL_PLANAR
#ifdef Z_PROBE_END_SCRIPT
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT);
#endif
enqueue_and_echo_commands_P(PSTR(Z_PROBE_END_SCRIPT));
stepper.synchronize();
#endif
// Auto Bed Leveling is complete! Enable if possible.
planner.abl_enabled = dryrun ? abl_should_enable : true;
} // !isnan(measured_z)
// Restore state after probing
if (!faux) clean_up_after_endstop_or_probe_move();
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< gcode_G29");
#endif
report_current_position();
KEEPALIVE_STATE(IN_HANDLER);
if (planner.abl_enabled)
SYNC_PLAN_POSITION_KINEMATIC();
}

View File

@ -0,0 +1,200 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#include "../../libs/buzzer.h"
#include "../../lcd/ultralcd.h"
// Save 130 bytes with non-duplication of PSTR
void echo_not_entered() { SERIAL_PROTOCOLLNPGM(" not entered."); }
void mbl_mesh_report() {
SERIAL_PROTOCOLLNPGM("Num X,Y: " STRINGIFY(GRID_MAX_POINTS_X) "," STRINGIFY(GRID_MAX_POINTS_Y));
SERIAL_PROTOCOLPGM("Z offset: "); SERIAL_PROTOCOL_F(mbl.z_offset, 5);
SERIAL_PROTOCOLLNPGM("\nMeasured points:");
print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 5,
[](const uint8_t ix, const uint8_t iy) { return mbl.z_values[ix][iy]; }
);
}
void mesh_probing_done() {
mbl.set_has_mesh(true);
home_all_axes();
set_bed_leveling_enabled(true);
#if ENABLED(MESH_G28_REST_ORIGIN)
current_position[Z_AXIS] = LOGICAL_Z_POSITION(Z_MIN_POS);
set_destination_to_current();
line_to_destination(homing_feedrate(Z_AXIS));
stepper.synchronize();
#endif
}
/**
* G29: Mesh-based Z probe, probes a grid and produces a
* mesh to compensate for variable bed height
*
* Parameters With MESH_BED_LEVELING:
*
* S0 Produce a mesh report
* S1 Start probing mesh points
* S2 Probe the next mesh point
* S3 Xn Yn Zn.nn Manually modify a single point
* S4 Zn.nn Set z offset. Positive away from bed, negative closer to bed.
* S5 Reset and disable mesh
*
* The S0 report the points as below
*
* +----> X-axis 1-n
* |
* |
* v Y-axis 1-n
*
*/
void gcode_G29() {
static int mbl_probe_index = -1;
#if HAS_SOFTWARE_ENDSTOPS
static bool enable_soft_endstops;
#endif
const MeshLevelingState state = (MeshLevelingState)parser.byteval('S', (int8_t)MeshReport);
if (!WITHIN(state, 0, 5)) {
SERIAL_PROTOCOLLNPGM("S out of range (0-5).");
return;
}
int8_t px, py;
switch (state) {
case MeshReport:
if (leveling_is_valid()) {
SERIAL_PROTOCOLLNPAIR("State: ", leveling_is_active() ? MSG_ON : MSG_OFF);
mbl_mesh_report();
}
else
SERIAL_PROTOCOLLNPGM("Mesh bed leveling has no data.");
break;
case MeshStart:
mbl.reset();
mbl_probe_index = 0;
enqueue_and_echo_commands_P(PSTR("G28\nG29 S2"));
break;
case MeshNext:
if (mbl_probe_index < 0) {
SERIAL_PROTOCOLLNPGM("Start mesh probing with \"G29 S1\" first.");
return;
}
// For each G29 S2...
if (mbl_probe_index == 0) {
#if HAS_SOFTWARE_ENDSTOPS
// For the initial G29 S2 save software endstop state
enable_soft_endstops = soft_endstops_enabled;
#endif
}
else {
// For G29 S2 after adjusting Z.
mbl.set_zigzag_z(mbl_probe_index - 1, current_position[Z_AXIS]);
#if HAS_SOFTWARE_ENDSTOPS
soft_endstops_enabled = enable_soft_endstops;
#endif
}
// If there's another point to sample, move there with optional lift.
if (mbl_probe_index < GRID_MAX_POINTS) {
mbl.zigzag(mbl_probe_index, px, py);
_manual_goto_xy(mbl.index_to_xpos[px], mbl.index_to_ypos[py]);
#if HAS_SOFTWARE_ENDSTOPS
// Disable software endstops to allow manual adjustment
// If G29 is not completed, they will not be re-enabled
soft_endstops_enabled = false;
#endif
mbl_probe_index++;
}
else {
// One last "return to the bed" (as originally coded) at completion
current_position[Z_AXIS] = LOGICAL_Z_POSITION(Z_MIN_POS) + MANUAL_PROBE_HEIGHT;
line_to_current_position();
stepper.synchronize();
// After recording the last point, activate home and activate
mbl_probe_index = -1;
SERIAL_PROTOCOLLNPGM("Mesh probing done.");
BUZZ(100, 659);
BUZZ(100, 698);
mesh_probing_done();
}
break;
case MeshSet:
if (parser.seenval('X')) {
px = parser.value_int() - 1;
if (!WITHIN(px, 0, GRID_MAX_POINTS_X - 1)) {
SERIAL_PROTOCOLLNPGM("X out of range (1-" STRINGIFY(GRID_MAX_POINTS_X) ").");
return;
}
}
else {
SERIAL_CHAR('X'); echo_not_entered();
return;
}
if (parser.seenval('Y')) {
py = parser.value_int() - 1;
if (!WITHIN(py, 0, GRID_MAX_POINTS_Y - 1)) {
SERIAL_PROTOCOLLNPGM("Y out of range (1-" STRINGIFY(GRID_MAX_POINTS_Y) ").");
return;
}
}
else {
SERIAL_CHAR('Y'); echo_not_entered();
return;
}
if (parser.seenval('Z')) {
mbl.z_values[px][py] = parser.value_linear_units();
}
else {
SERIAL_CHAR('Z'); echo_not_entered();
return;
}
break;
case MeshSetZOffset:
if (parser.seenval('Z')) {
mbl.z_offset = parser.value_linear_units();
}
else {
SERIAL_CHAR('Z'); echo_not_entered();
return;
}
break;
case MeshReset:
reset_bed_level();
break;
} // switch(state)
report_current_position();
}

View File

@ -0,0 +1,27 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
void gcode_G29() {
ubl.G29();
}

View File

@ -0,0 +1,65 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#if ENABLED(MESH_BED_LEVELING) || ENABLED(PROBE_MANUALLY)
#if ENABLED(PROBE_MANUALLY) && ENABLED(LCD_BED_LEVELING)
extern bool lcd_wait_for_move;
#endif
inline void _manual_goto_xy(const float &x, const float &y) {
const float old_feedrate_mm_s = feedrate_mm_s;
#if MANUAL_PROBE_HEIGHT > 0
const float prev_z = current_position[Z_AXIS];
feedrate_mm_s = homing_feedrate(Z_AXIS);
current_position[Z_AXIS] = LOGICAL_Z_POSITION(MANUAL_PROBE_HEIGHT);
line_to_current_position();
#endif
feedrate_mm_s = MMM_TO_MMS(XY_PROBE_SPEED);
current_position[X_AXIS] = LOGICAL_X_POSITION(x);
current_position[Y_AXIS] = LOGICAL_Y_POSITION(y);
line_to_current_position();
#if MANUAL_PROBE_HEIGHT > 0
feedrate_mm_s = homing_feedrate(Z_AXIS);
current_position[Z_AXIS] = prev_z; // move back to the previous Z.
line_to_current_position();
#endif
feedrate_mm_s = old_feedrate_mm_s;
stepper.synchronize();
#if ENABLED(PROBE_MANUALLY) && ENABLED(LCD_BED_LEVELING)
lcd_wait_for_move = false;
#endif
}
#endif
#if ENABLED(MESH_BED_LEVELING)
#include "G29-mbl.h"
#elif ENABLED(AUTO_BED_LEVELING_UBL)
#include "G29-ubl.h"
#elif HAS_ABL
#include "G29-abl.h"
#endif

View File

@ -0,0 +1,451 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#include "common.h"
#if HOTENDS > 1
#include "../control/tool_change.h"
#endif
/**
* G33 - Delta '1-4-7-point' Auto-Calibration
* Calibrate height, endstops, delta radius, and tower angles.
*
* Parameters:
*
* Pn Number of probe points:
*
* P1 Probe center and set height only.
* P2 Probe center and towers. Set height, endstops, and delta radius.
* P3 Probe all positions: center, towers and opposite towers. Set all.
* P4-P7 Probe all positions at different locations and average them.
*
* T0 Don't calibrate tower angle corrections
*
* Cn.nn Calibration precision; when omitted calibrates to maximum precision
*
* Fn Force to run at least n iterations and takes the best result
*
* Vn Verbose level:
*
* V0 Dry-run mode. Report settings and probe results. No calibration.
* V1 Report settings
* V2 Report settings and probe results
*
* E Engage the probe for each point
*/
void print_signed_float(const char * const prefix, const float &f) {
SERIAL_PROTOCOLPGM(" ");
serialprintPGM(prefix);
SERIAL_PROTOCOLCHAR(':');
if (f >= 0) SERIAL_CHAR('+');
SERIAL_PROTOCOL_F(f, 2);
}
inline void print_G33_settings(const bool end_stops, const bool tower_angles){ // TODO echo these to LCD ???
SERIAL_PROTOCOLPAIR(".Height:", DELTA_HEIGHT + home_offset[Z_AXIS]);
if (end_stops) {
print_signed_float(PSTR(" Ex"), endstop_adj[A_AXIS]);
print_signed_float(PSTR("Ey"), endstop_adj[B_AXIS]);
print_signed_float(PSTR("Ez"), endstop_adj[C_AXIS]);
SERIAL_PROTOCOLPAIR(" Radius:", delta_radius);
}
SERIAL_EOL();
if (tower_angles) {
SERIAL_PROTOCOLPGM(".Tower angle : ");
print_signed_float(PSTR("Tx"), delta_tower_angle_trim[A_AXIS]);
print_signed_float(PSTR("Ty"), delta_tower_angle_trim[B_AXIS]);
SERIAL_PROTOCOLLNPGM(" Tz:+0.00");
}
}
void G33_cleanup(
#if HOTENDS > 1
const uint8_t old_tool_index
#endif
) {
#if ENABLED(DELTA_HOME_TO_SAFE_ZONE)
do_blocking_move_to_z(delta_clip_start_height);
#endif
STOW_PROBE();
clean_up_after_endstop_or_probe_move();
#if HOTENDS > 1
tool_change(old_tool_index, 0, true);
#endif
}
void gcode_G33() {
const int8_t probe_points = parser.intval('P', DELTA_CALIBRATION_DEFAULT_POINTS);
if (!WITHIN(probe_points, 1, 7)) {
SERIAL_PROTOCOLLNPGM("?(P)oints is implausible (1-7).");
return;
}
const int8_t verbose_level = parser.byteval('V', 1);
if (!WITHIN(verbose_level, 0, 2)) {
SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-2).");
return;
}
const float calibration_precision = parser.floatval('C');
if (calibration_precision < 0) {
SERIAL_PROTOCOLLNPGM("?(C)alibration precision is implausible (>0).");
return;
}
const int8_t force_iterations = parser.intval('F', 0);
if (!WITHIN(force_iterations, 0, 30)) {
SERIAL_PROTOCOLLNPGM("?(F)orce iteration is implausible (0-30).");
return;
}
const bool towers_set = parser.boolval('T', true),
stow_after_each = parser.boolval('E'),
_1p_calibration = probe_points == 1,
_4p_calibration = probe_points == 2,
_4p_towers_points = _4p_calibration && towers_set,
_4p_opposite_points = _4p_calibration && !towers_set,
_7p_calibration = probe_points >= 3,
_7p_half_circle = probe_points == 3,
_7p_double_circle = probe_points == 5,
_7p_triple_circle = probe_points == 6,
_7p_quadruple_circle = probe_points == 7,
_7p_multi_circle = _7p_double_circle || _7p_triple_circle || _7p_quadruple_circle,
_7p_intermed_points = _7p_calibration && !_7p_half_circle;
const static char save_message[] PROGMEM = "Save with M500 and/or copy to Configuration.h";
const float dx = (X_PROBE_OFFSET_FROM_EXTRUDER),
dy = (Y_PROBE_OFFSET_FROM_EXTRUDER);
int8_t iterations = 0;
float test_precision,
zero_std_dev = (verbose_level ? 999.0 : 0.0), // 0.0 in dry-run mode : forced end
zero_std_dev_old = zero_std_dev,
zero_std_dev_min = zero_std_dev,
e_old[XYZ] = {
endstop_adj[A_AXIS],
endstop_adj[B_AXIS],
endstop_adj[C_AXIS]
},
dr_old = delta_radius,
zh_old = home_offset[Z_AXIS],
alpha_old = delta_tower_angle_trim[A_AXIS],
beta_old = delta_tower_angle_trim[B_AXIS];
if (!_1p_calibration) { // test if the outer radius is reachable
const float circles = (_7p_quadruple_circle ? 1.5 :
_7p_triple_circle ? 1.0 :
_7p_double_circle ? 0.5 : 0),
r = (1 + circles * 0.1) * delta_calibration_radius;
for (uint8_t axis = 1; axis < 13; ++axis) {
const float a = RADIANS(180 + 30 * axis);
if (!position_is_reachable_xy(cos(a) * r, sin(a) * r)) {
SERIAL_PROTOCOLLNPGM("?(M665 B)ed radius is implausible.");
return;
}
}
}
SERIAL_PROTOCOLLNPGM("G33 Auto Calibrate");
stepper.synchronize();
#if HAS_LEVELING
reset_bed_level(); // After calibration bed-level data is no longer valid
#endif
#if HOTENDS > 1
const uint8_t old_tool_index = active_extruder;
tool_change(0, 0, true);
#define G33_CLEANUP() G33_cleanup(old_tool_index)
#else
#define G33_CLEANUP() G33_cleanup()
#endif
setup_for_endstop_or_probe_move();
endstops.enable(true);
if (!home_delta())
return;
endstops.not_homing();
// print settings
const char *checkingac = PSTR("Checking... AC"); // TODO: Make translatable string
serialprintPGM(checkingac);
if (verbose_level == 0) SERIAL_PROTOCOLPGM(" (DRY-RUN)");
SERIAL_EOL();
lcd_setstatusPGM(checkingac);
print_G33_settings(!_1p_calibration, _7p_calibration && towers_set);
#if DISABLED(PROBE_MANUALLY)
const float measured_z = probe_pt(dx, dy, stow_after_each, 1, false); // 1st probe to set height
if (isnan(measured_z)) return G33_CLEANUP();
home_offset[Z_AXIS] -= measured_z;
#endif
do {
float z_at_pt[13] = { 0.0 };
test_precision = zero_std_dev_old != 999.0 ? (zero_std_dev + zero_std_dev_old) / 2 : zero_std_dev;
iterations++;
// Probe the points
if (!_7p_half_circle && !_7p_triple_circle) { // probe the center
#if ENABLED(PROBE_MANUALLY)
z_at_pt[0] += lcd_probe_pt(0, 0);
#else
z_at_pt[0] += probe_pt(dx, dy, stow_after_each, 1, false);
if (isnan(z_at_pt[0])) return G33_CLEANUP();
#endif
}
if (_7p_calibration) { // probe extra center points
for (int8_t axis = _7p_multi_circle ? 11 : 9; axis > 0; axis -= _7p_multi_circle ? 2 : 4) {
const float a = RADIANS(180 + 30 * axis), r = delta_calibration_radius * 0.1;
#if ENABLED(PROBE_MANUALLY)
z_at_pt[0] += lcd_probe_pt(cos(a) * r, sin(a) * r);
#else
z_at_pt[0] += probe_pt(cos(a) * r + dx, sin(a) * r + dy, stow_after_each, 1);
if (isnan(z_at_pt[0])) return G33_CLEANUP();
#endif
}
z_at_pt[0] /= float(_7p_double_circle ? 7 : probe_points);
}
if (!_1p_calibration) { // probe the radius
bool zig_zag = true;
const uint8_t start = _4p_opposite_points ? 3 : 1,
step = _4p_calibration ? 4 : _7p_half_circle ? 2 : 1;
for (uint8_t axis = start; axis < 13; axis += step) {
const float zigadd = (zig_zag ? 0.5 : 0.0),
offset_circles = _7p_quadruple_circle ? zigadd + 1.0 :
_7p_triple_circle ? zigadd + 0.5 :
_7p_double_circle ? zigadd : 0;
for (float circles = -offset_circles ; circles <= offset_circles; circles++) {
const float a = RADIANS(180 + 30 * axis),
r = delta_calibration_radius * (1 + circles * (zig_zag ? 0.1 : -0.1));
#if ENABLED(PROBE_MANUALLY)
z_at_pt[axis] += lcd_probe_pt(cos(a) * r, sin(a) * r);
#else
z_at_pt[axis] += probe_pt(cos(a) * r + dx, sin(a) * r + dy, stow_after_each, 1);
if (isnan(z_at_pt[axis])) return G33_CLEANUP();
#endif
}
zig_zag = !zig_zag;
z_at_pt[axis] /= (2 * offset_circles + 1);
}
}
if (_7p_intermed_points) // average intermediates to tower and opposites
for (uint8_t axis = 1; axis < 13; axis += 2)
z_at_pt[axis] = (z_at_pt[axis] + (z_at_pt[axis + 1] + z_at_pt[(axis + 10) % 12 + 1]) / 2.0) / 2.0;
float S1 = z_at_pt[0],
S2 = sq(z_at_pt[0]);
int16_t N = 1;
if (!_1p_calibration) // std dev from zero plane
for (uint8_t axis = (_4p_opposite_points ? 3 : 1); axis < 13; axis += (_4p_calibration ? 4 : 2)) {
S1 += z_at_pt[axis];
S2 += sq(z_at_pt[axis]);
N++;
}
zero_std_dev_old = zero_std_dev;
zero_std_dev = round(SQRT(S2 / N) * 1000.0) / 1000.0 + 0.00001;
// Solve matrices
if ((zero_std_dev < test_precision && zero_std_dev > calibration_precision) || iterations <= force_iterations) {
if (zero_std_dev < zero_std_dev_min) {
COPY(e_old, endstop_adj);
dr_old = delta_radius;
zh_old = home_offset[Z_AXIS];
alpha_old = delta_tower_angle_trim[A_AXIS];
beta_old = delta_tower_angle_trim[B_AXIS];
}
float e_delta[XYZ] = { 0.0 }, r_delta = 0.0, t_alpha = 0.0, t_beta = 0.0;
const float r_diff = delta_radius - delta_calibration_radius,
h_factor = 1.00 + r_diff * 0.001, //1.02 for r_diff = 20mm
r_factor = -(1.75 + 0.005 * r_diff + 0.001 * sq(r_diff)), //2.25 for r_diff = 20mm
a_factor = 100.0 / delta_calibration_radius; //1.25 for cal_rd = 80mm
#define ZP(N,I) ((N) * z_at_pt[I])
#define Z1000(I) ZP(1.00, I)
#define Z1050(I) ZP(h_factor, I)
#define Z0700(I) ZP(h_factor * 2.0 / 3.00, I)
#define Z0350(I) ZP(h_factor / 3.00, I)
#define Z0175(I) ZP(h_factor / 6.00, I)
#define Z2250(I) ZP(r_factor, I)
#define Z0750(I) ZP(r_factor / 3.00, I)
#define Z0375(I) ZP(r_factor / 6.00, I)
#define Z0444(I) ZP(a_factor * 4.0 / 9.0, I)
#define Z0888(I) ZP(a_factor * 8.0 / 9.0, I)
#if ENABLED(PROBE_MANUALLY)
test_precision = 0.00; // forced end
#endif
switch (probe_points) {
case 1:
test_precision = 0.00; // forced end
LOOP_XYZ(i) e_delta[i] = Z1000(0);
break;
case 2:
if (towers_set) {
e_delta[X_AXIS] = Z1050(0) + Z0700(1) - Z0350(5) - Z0350(9);
e_delta[Y_AXIS] = Z1050(0) - Z0350(1) + Z0700(5) - Z0350(9);
e_delta[Z_AXIS] = Z1050(0) - Z0350(1) - Z0350(5) + Z0700(9);
r_delta = Z2250(0) - Z0750(1) - Z0750(5) - Z0750(9);
}
else {
e_delta[X_AXIS] = Z1050(0) - Z0700(7) + Z0350(11) + Z0350(3);
e_delta[Y_AXIS] = Z1050(0) + Z0350(7) - Z0700(11) + Z0350(3);
e_delta[Z_AXIS] = Z1050(0) + Z0350(7) + Z0350(11) - Z0700(3);
r_delta = Z2250(0) - Z0750(7) - Z0750(11) - Z0750(3);
}
break;
default:
e_delta[X_AXIS] = Z1050(0) + Z0350(1) - Z0175(5) - Z0175(9) - Z0350(7) + Z0175(11) + Z0175(3);
e_delta[Y_AXIS] = Z1050(0) - Z0175(1) + Z0350(5) - Z0175(9) + Z0175(7) - Z0350(11) + Z0175(3);
e_delta[Z_AXIS] = Z1050(0) - Z0175(1) - Z0175(5) + Z0350(9) + Z0175(7) + Z0175(11) - Z0350(3);
r_delta = Z2250(0) - Z0375(1) - Z0375(5) - Z0375(9) - Z0375(7) - Z0375(11) - Z0375(3);
if (towers_set) {
t_alpha = Z0444(1) - Z0888(5) + Z0444(9) + Z0444(7) - Z0888(11) + Z0444(3);
t_beta = Z0888(1) - Z0444(5) - Z0444(9) + Z0888(7) - Z0444(11) - Z0444(3);
}
break;
}
LOOP_XYZ(axis) endstop_adj[axis] += e_delta[axis];
delta_radius += r_delta;
delta_tower_angle_trim[A_AXIS] += t_alpha;
delta_tower_angle_trim[B_AXIS] += t_beta;
// adjust delta_height and endstops by the max amount
const float z_temp = MAX3(endstop_adj[A_AXIS], endstop_adj[B_AXIS], endstop_adj[C_AXIS]);
home_offset[Z_AXIS] -= z_temp;
LOOP_XYZ(i) endstop_adj[i] -= z_temp;
recalc_delta_settings(delta_radius, delta_diagonal_rod);
}
else if (zero_std_dev >= test_precision) { // step one back
COPY(endstop_adj, e_old);
delta_radius = dr_old;
home_offset[Z_AXIS] = zh_old;
delta_tower_angle_trim[A_AXIS] = alpha_old;
delta_tower_angle_trim[B_AXIS] = beta_old;
recalc_delta_settings(delta_radius, delta_diagonal_rod);
}
NOMORE(zero_std_dev_min, zero_std_dev);
// print report
if (verbose_level != 1) {
SERIAL_PROTOCOLPGM(". ");
print_signed_float(PSTR("c"), z_at_pt[0]);
if (_4p_towers_points || _7p_calibration) {
print_signed_float(PSTR(" x"), z_at_pt[1]);
print_signed_float(PSTR(" y"), z_at_pt[5]);
print_signed_float(PSTR(" z"), z_at_pt[9]);
}
if (!_4p_opposite_points) SERIAL_EOL();
if ((_4p_opposite_points) || _7p_calibration) {
if (_7p_calibration) {
SERIAL_CHAR('.');
SERIAL_PROTOCOL_SP(13);
}
print_signed_float(PSTR(" yz"), z_at_pt[7]);
print_signed_float(PSTR("zx"), z_at_pt[11]);
print_signed_float(PSTR("xy"), z_at_pt[3]);
SERIAL_EOL();
}
}
if (verbose_level != 0) { // !dry run
if ((zero_std_dev >= test_precision || zero_std_dev <= calibration_precision) && iterations > force_iterations) { // end iterations
SERIAL_PROTOCOLPGM("Calibration OK");
SERIAL_PROTOCOL_SP(36);
#if DISABLED(PROBE_MANUALLY)
if (zero_std_dev >= test_precision && !_1p_calibration)
SERIAL_PROTOCOLPGM("rolling back.");
else
#endif
{
SERIAL_PROTOCOLPGM("std dev:");
SERIAL_PROTOCOL_F(zero_std_dev_min, 3);
}
SERIAL_EOL();
char mess[21];
sprintf_P(mess, PSTR("Calibration sd:"));
if (zero_std_dev_min < 1)
sprintf_P(&mess[15], PSTR("0.%03i"), (int)round(zero_std_dev_min * 1000.0));
else
sprintf_P(&mess[15], PSTR("%03i.x"), (int)round(zero_std_dev_min));
lcd_setstatus(mess);
print_G33_settings(!_1p_calibration, _7p_calibration && towers_set);
serialprintPGM(save_message);
SERIAL_EOL();
}
else { // !end iterations
char mess[15];
if (iterations < 31)
sprintf_P(mess, PSTR("Iteration : %02i"), (int)iterations);
else
sprintf_P(mess, PSTR("No convergence"));
SERIAL_PROTOCOL(mess);
SERIAL_PROTOCOL_SP(36);
SERIAL_PROTOCOLPGM("std dev:");
SERIAL_PROTOCOL_F(zero_std_dev, 3);
SERIAL_EOL();
lcd_setstatus(mess);
print_G33_settings(!_1p_calibration, _7p_calibration && towers_set);
}
}
else { // dry run
const char *enddryrun = PSTR("End DRY-RUN");
serialprintPGM(enddryrun);
SERIAL_PROTOCOL_SP(39);
SERIAL_PROTOCOLPGM("std dev:");
SERIAL_PROTOCOL_F(zero_std_dev, 3);
SERIAL_EOL();
char mess[21];
sprintf_P(mess, enddryrun);
sprintf_P(&mess[11], PSTR(" sd:"));
if (zero_std_dev < 1)
sprintf_P(&mess[15], PSTR("0.%03i"), (int)round(zero_std_dev * 1000.0));
else
sprintf_P(&mess[15], PSTR("%03i.x"), (int)round(zero_std_dev));
lcd_setstatus(mess);
}
endstops.enable(true);
home_delta();
endstops.not_homing();
}
while ((zero_std_dev < test_precision && zero_std_dev > calibration_precision && iterations < 31) || iterations <= force_iterations);
G33_CLEANUP();
}

View File

@ -0,0 +1,322 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#include "../../libs/hex_print_routines.h"
/**
* M100 Free Memory Watcher
*
* This code watches the free memory block between the bottom of the heap and the top of the stack.
* This memory block is initialized and watched via the M100 command.
*
* M100 I Initializes the free memory block and prints vitals statistics about the area
*
* M100 F Identifies how much of the free memory block remains free and unused. It also
* detects and reports any corruption within the free memory block that may have
* happened due to errant firmware.
*
* M100 D Does a hex display of the free memory block along with a flag for any errant
* data that does not match the expected value.
*
* M100 C x Corrupts x locations within the free memory block. This is useful to check the
* correctness of the M100 F and M100 D commands.
*
* Also, there are two support functions that can be called from a developer's C code.
*
* uint16_t check_for_free_memory_corruption(const char * const ptr);
* void M100_dump_routine(const char * const title, const char *start, const char *end);
*
* Initial version by Roxy-3D
*/
#define M100_FREE_MEMORY_DUMPER // Enable for the `M110 D` Dump sub-command
#define M100_FREE_MEMORY_CORRUPTOR // Enable for the `M100 C` Corrupt sub-command
#define TEST_BYTE ((char) 0xE5)
extern char command_queue[BUFSIZE][MAX_CMD_SIZE];
extern char* __brkval;
extern size_t __heap_start, __heap_end, __flp;
extern char __bss_end;
//
// Utility functions
//
#define END_OF_HEAP() (__brkval ? __brkval : &__bss_end)
// Location of a variable on its stack frame. Returns a value above
// the stack (once the function returns to the caller).
char* top_of_stack() {
char x;
return &x + 1; // x is pulled on return;
}
// Count the number of test bytes at the specified location.
int16_t count_test_bytes(const char * const ptr) {
for (uint16_t i = 0; i < 32000; i++)
if (((char) ptr[i]) != TEST_BYTE)
return i - 1;
return -1;
}
//
// M100 sub-commands
//
#if ENABLED(M100_FREE_MEMORY_DUMPER)
/**
* M100 D
* Dump the free memory block from __brkval to the stack pointer.
* malloc() eats memory from the start of the block and the stack grows
* up from the bottom of the block. Solid test bytes indicate nothing has
* used that memory yet. There should not be anything but test bytes within
* the block. If so, it may indicate memory corruption due to a bad pointer.
* Unexpected bytes are flagged in the right column.
*/
void dump_free_memory(const char *ptr, const char *sp) {
//
// Start and end the dump on a nice 16 byte boundary
// (even though the values are not 16-byte aligned).
//
ptr = (char *)((uint16_t)ptr & 0xFFF0); // Align to 16-byte boundary
sp = (char *)((uint16_t)sp | 0x000F); // Align sp to the 15th byte (at or above sp)
// Dump command main loop
while (ptr < sp) {
print_hex_word((uint16_t)ptr); // Print the address
SERIAL_CHAR(':');
for (uint8_t i = 0; i < 16; i++) { // and 16 data bytes
if (i == 8) SERIAL_CHAR('-');
print_hex_byte(ptr[i]);
SERIAL_CHAR(' ');
}
safe_delay(25);
SERIAL_CHAR('|'); // Point out non test bytes
for (uint8_t i = 0; i < 16; i++) {
char ccc = (char)ptr[i]; // cast to char before automatically casting to char on assignment, in case the compiler is broken
if (&ptr[i] >= (const char*)command_queue && &ptr[i] < (const char*)(command_queue + sizeof(command_queue))) { // Print out ASCII in the command buffer area
if (!WITHIN(ccc, ' ', 0x7E)) ccc = ' ';
}
else { // If not in the command buffer area, flag bytes that don't match the test byte
ccc = (ccc == TEST_BYTE) ? ' ' : '?';
}
SERIAL_CHAR(ccc);
}
SERIAL_EOL();
ptr += 16;
safe_delay(25);
idle();
}
}
void M100_dump_routine(const char * const title, const char *start, const char *end) {
SERIAL_ECHOLN(title);
//
// Round the start and end locations to produce full lines of output
//
start = (char*)((uint16_t) start & 0xFFF0);
end = (char*)((uint16_t) end | 0x000F);
dump_free_memory(start, end);
}
#endif // M100_FREE_MEMORY_DUMPER
int check_for_free_memory_corruption(const char * const title) {
SERIAL_ECHO(title);
char *ptr = END_OF_HEAP(), *sp = top_of_stack();
int n = sp - ptr;
SERIAL_ECHOPAIR("\nfmc() n=", n);
SERIAL_ECHOPAIR("\n&__brkval: ", hex_address(&__brkval));
SERIAL_ECHOPAIR("=", hex_address(__brkval));
SERIAL_ECHOPAIR("\n__bss_end: ", hex_address(&__bss_end));
SERIAL_ECHOPAIR(" sp=", hex_address(sp));
if (sp < ptr) {
SERIAL_ECHOPGM(" sp < Heap ");
// SET_INPUT_PULLUP(63); // if the developer has a switch wired up to their controller board
// safe_delay(5); // this code can be enabled to pause the display as soon as the
// while ( READ(63)) // malfunction is detected. It is currently defaulting to a switch
// idle(); // being on pin-63 which is unassigend and available on most controller
// safe_delay(20); // boards.
// while ( !READ(63))
// idle();
safe_delay(20);
#if ENABLED(M100_FREE_MEMORY_DUMPER)
M100_dump_routine(" Memory corruption detected with sp<Heap\n", (char*)0x1B80, (char*)0x21FF);
#endif
}
// Scan through the range looking for the biggest block of 0xE5's we can find
int block_cnt = 0;
for (int i = 0; i < n; i++) {
if (ptr[i] == TEST_BYTE) {
int16_t j = count_test_bytes(ptr + i);
if (j > 8) {
// SERIAL_ECHOPAIR("Found ", j);
// SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(ptr + i));
i += j;
block_cnt++;
SERIAL_ECHOPAIR(" (", block_cnt);
SERIAL_ECHOPAIR(") found=", j);
SERIAL_ECHOPGM(" ");
}
}
}
SERIAL_ECHOPAIR(" block_found=", block_cnt);
if (block_cnt != 1 || __brkval != 0x0000)
SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area.");
if (block_cnt == 0) // Make sure the special case of no free blocks shows up as an
block_cnt = -1; // error to the calling code!
SERIAL_ECHOPGM(" return=");
if (block_cnt == 1) {
SERIAL_CHAR('0'); // if the block_cnt is 1, nothing has broken up the free memory
SERIAL_EOL(); // area and it is appropriate to say 'no corruption'.
return 0;
}
SERIAL_ECHOLNPGM("true");
return block_cnt;
}
/**
* M100 F
* Return the number of free bytes in the memory pool,
* with other vital statistics defining the pool.
*/
void free_memory_pool_report(char * const ptr, const int16_t size) {
int16_t max_cnt = -1, block_cnt = 0;
char *max_addr = NULL;
// Find the longest block of test bytes in the buffer
for (int16_t i = 0; i < size; i++) {
char *addr = ptr + i;
if (*addr == TEST_BYTE) {
const int16_t j = count_test_bytes(addr);
if (j > 8) {
SERIAL_ECHOPAIR("Found ", j);
SERIAL_ECHOLNPAIR(" bytes free at ", hex_address(addr));
if (j > max_cnt) {
max_cnt = j;
max_addr = addr;
}
i += j;
block_cnt++;
}
}
}
if (block_cnt > 1) {
SERIAL_ECHOLNPGM("\nMemory Corruption detected in free memory area.");
SERIAL_ECHOPAIR("\nLargest free block is ", max_cnt);
SERIAL_ECHOLNPAIR(" bytes at ", hex_address(max_addr));
}
SERIAL_ECHOLNPAIR("check_for_free_memory_corruption() = ", check_for_free_memory_corruption("M100 F "));
}
#if ENABLED(M100_FREE_MEMORY_CORRUPTOR)
/**
* M100 C<num>
* Corrupt <num> locations in the free memory pool and report the corrupt addresses.
* This is useful to check the correctness of the M100 D and the M100 F commands.
*/
void corrupt_free_memory(char *ptr, const uint16_t size) {
ptr += 8;
const uint16_t near_top = top_of_stack() - ptr - 250, // -250 to avoid interrupt activity that's altered the stack.
j = near_top / (size + 1);
SERIAL_ECHOLNPGM("Corrupting free memory block.\n");
for (uint16_t i = 1; i <= size; i++) {
char * const addr = ptr + i * j;
*addr = i;
SERIAL_ECHOPAIR("\nCorrupting address: ", hex_address(addr));
}
SERIAL_EOL();
}
#endif // M100_FREE_MEMORY_CORRUPTOR
/**
* M100 I
* Init memory for the M100 tests. (Automatically applied on the first M100.)
*/
void init_free_memory(char *ptr, int16_t size) {
SERIAL_ECHOLNPGM("Initializing free memory block.\n\n");
size -= 250; // -250 to avoid interrupt activity that's altered the stack.
if (size < 0) {
SERIAL_ECHOLNPGM("Unable to initialize.\n");
return;
}
ptr += 8; // move a few bytes away from the heap just because we don't want
// to be altering memory that close to it.
memset(ptr, TEST_BYTE, size);
SERIAL_ECHO(size);
SERIAL_ECHOLNPGM(" bytes of memory initialized.\n");
for (int16_t i = 0; i < size; i++) {
if (ptr[i] != TEST_BYTE) {
SERIAL_ECHOPAIR("? address : ", hex_address(ptr + i));
SERIAL_ECHOLNPAIR("=", hex_byte(ptr[i]));
SERIAL_EOL();
}
}
}
/**
* M100: Free Memory Check
*/
void gcode_M100() {
SERIAL_ECHOPAIR("\n__brkval : ", hex_address(__brkval));
SERIAL_ECHOPAIR("\n__bss_end : ", hex_address(&__bss_end));
char *ptr = END_OF_HEAP(), *sp = top_of_stack();
SERIAL_ECHOPAIR("\nstart of free space : ", hex_address(ptr));
SERIAL_ECHOLNPAIR("\nStack Pointer : ", hex_address(sp));
// Always init on the first invocation of M100
static bool m100_not_initialized = true;
if (m100_not_initialized || parser.seen('I')) {
m100_not_initialized = false;
init_free_memory(ptr, sp - ptr);
}
#if ENABLED(M100_FREE_MEMORY_DUMPER)
if (parser.seen('D'))
return dump_free_memory(ptr, sp);
#endif
if (parser.seen('F'))
return free_memory_pool_report(ptr, sp - ptr);
#if ENABLED(M100_FREE_MEMORY_CORRUPTOR)
if (parser.seen('C'))
return corrupt_free_memory(ptr, parser.value_int());
#endif
}

View File

@ -0,0 +1,121 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
/**
* M420: Enable/Disable Bed Leveling and/or set the Z fade height.
*
* S[bool] Turns leveling on or off
* Z[height] Sets the Z fade height (0 or none to disable)
* V[bool] Verbose - Print the leveling grid
*
* With AUTO_BED_LEVELING_UBL only:
*
* L[index] Load UBL mesh from index (0 is default)
*/
void gcode_M420() {
#if ENABLED(AUTO_BED_LEVELING_UBL)
// L to load a mesh from the EEPROM
if (parser.seen('L')) {
#if ENABLED(EEPROM_SETTINGS)
const int8_t storage_slot = parser.has_value() ? parser.value_int() : ubl.state.storage_slot;
const int16_t a = settings.calc_num_meshes();
if (!a) {
SERIAL_PROTOCOLLNPGM("?EEPROM storage not available.");
return;
}
if (!WITHIN(storage_slot, 0, a - 1)) {
SERIAL_PROTOCOLLNPGM("?Invalid storage slot.");
SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1);
return;
}
settings.load_mesh(storage_slot);
ubl.state.storage_slot = storage_slot;
#else
SERIAL_PROTOCOLLNPGM("?EEPROM storage not available.");
return;
#endif
}
// L to load a mesh from the EEPROM
if (parser.seen('L') || parser.seen('V')) {
ubl.display_map(0); // Currently only supports one map type
SERIAL_ECHOLNPAIR("UBL_MESH_VALID = ", UBL_MESH_VALID);
SERIAL_ECHOLNPAIR("ubl.state.storage_slot = ", ubl.state.storage_slot);
}
#endif // AUTO_BED_LEVELING_UBL
// V to print the matrix or mesh
if (parser.seen('V')) {
#if ABL_PLANAR
planner.bed_level_matrix.debug(PSTR("Bed Level Correction Matrix:"));
#else
if (leveling_is_valid()) {
#if ENABLED(AUTO_BED_LEVELING_BILINEAR)
print_bilinear_leveling_grid();
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
print_bilinear_leveling_grid_virt();
#endif
#elif ENABLED(MESH_BED_LEVELING)
SERIAL_ECHOLNPGM("Mesh Bed Level data:");
mbl_mesh_report();
#endif
}
#endif
}
const bool to_enable = parser.boolval('S');
if (parser.seen('S'))
set_bed_leveling_enabled(to_enable);
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
if (parser.seen('Z')) set_z_fade_height(parser.value_linear_units());
#endif
const bool new_status = leveling_is_active();
if (to_enable && !new_status) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_M420_FAILED);
}
SERIAL_ECHO_START();
SERIAL_ECHOLNPAIR("Bed Leveling ", new_status ? MSG_ON : MSG_OFF);
#if ENABLED(ENABLE_LEVELING_FADE_HEIGHT)
SERIAL_ECHO_START();
SERIAL_ECHOPGM("Fade Height ");
if (planner.z_fade_height > 0.0)
SERIAL_ECHOLN(planner.z_fade_height);
else
SERIAL_ECHOLNPGM(MSG_OFF);
#endif
}

View File

@ -0,0 +1,51 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
/**
* M421: Set a single Mesh Bed Leveling Z coordinate
*
* Usage:
* M421 I<xindex> J<yindex> Z<linear>
* M421 I<xindex> J<yindex> Q<offset>
*/
void gcode_M421() {
int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1);
const bool hasI = ix >= 0,
hasJ = iy >= 0,
hasZ = parser.seen('Z'),
hasQ = !hasZ && parser.seen('Q');
if (!hasI || !hasJ || !(hasZ || hasQ)) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS);
}
else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY);
}
else {
z_values[ix][iy] = parser.value_linear_units() + (hasQ ? z_values[ix][iy] : 0);
#if ENABLED(ABL_BILINEAR_SUBDIVISION)
bed_level_virt_interpolate();
#endif
}
}

View File

@ -0,0 +1,49 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
/**
* M421: Set a single Mesh Bed Leveling Z coordinate
*
* Usage:
* M421 X<linear> Y<linear> Z<linear>
* M421 X<linear> Y<linear> Q<offset>
* M421 I<xindex> J<yindex> Z<linear>
* M421 I<xindex> J<yindex> Q<offset>
*/
void gcode_M421() {
const bool hasX = parser.seen('X'), hasI = parser.seen('I');
const int8_t ix = hasI ? parser.value_int() : hasX ? mbl.probe_index_x(RAW_X_POSITION(parser.value_linear_units())) : -1;
const bool hasY = parser.seen('Y'), hasJ = parser.seen('J');
const int8_t iy = hasJ ? parser.value_int() : hasY ? mbl.probe_index_y(RAW_Y_POSITION(parser.value_linear_units())) : -1;
const bool hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q');
if (int(hasI && hasJ) + int(hasX && hasY) != 1 || !(hasZ || hasQ)) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS);
}
else if (ix < 0 || iy < 0) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY);
}
else
mbl.set_z(ix, iy, parser.value_linear_units() + (hasQ ? mbl.z_values[ix][iy] : 0));
}

View File

@ -0,0 +1,56 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
/**
* M421: Set a single Mesh Bed Leveling Z coordinate
*
* Usage:
* M421 I<xindex> J<yindex> Z<linear>
* M421 I<xindex> J<yindex> Q<offset>
* M421 C Z<linear>
* M421 C Q<offset>
*/
void gcode_M421() {
int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1);
const bool hasI = ix >= 0,
hasJ = iy >= 0,
hasC = parser.seen('C'),
hasZ = parser.seen('Z'),
hasQ = !hasZ && parser.seen('Q');
if (hasC) {
const mesh_index_pair location = ubl.find_closest_mesh_point_of_type(REAL, current_position[X_AXIS], current_position[Y_AXIS], USE_NOZZLE_AS_REFERENCE, NULL, false);
ix = location.x_index;
iy = location.y_index;
}
if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ)) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS);
}
else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY);
}
else
ubl.z_values[ix][iy] = parser.value_linear_units() + (hasQ ? ubl.z_values[ix][iy] : 0);
}

View File

@ -0,0 +1,273 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
/**
* M48: Z probe repeatability measurement function.
*
* Usage:
* M48 <P#> <X#> <Y#> <V#> <E> <L#>
* P = Number of sampled points (4-50, default 10)
* X = Sample X position
* Y = Sample Y position
* V = Verbose level (0-4, default=1)
* E = Engage Z probe for each reading
* L = Number of legs of movement before probe
* S = Schizoid (Or Star if you prefer)
*
* This function assumes the bed has been homed. Specifically, that a G28 command
* as been issued prior to invoking the M48 Z probe repeatability measurement function.
* Any information generated by a prior G29 Bed leveling command will be lost and need to be
* regenerated.
*/
void gcode_M48() {
if (axis_unhomed_error()) return;
const int8_t verbose_level = parser.byteval('V', 1);
if (!WITHIN(verbose_level, 0, 4)) {
SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-4).");
return;
}
if (verbose_level > 0)
SERIAL_PROTOCOLLNPGM("M48 Z-Probe Repeatability Test");
const int8_t n_samples = parser.byteval('P', 10);
if (!WITHIN(n_samples, 4, 50)) {
SERIAL_PROTOCOLLNPGM("?Sample size not plausible (4-50).");
return;
}
const bool stow_probe_after_each = parser.boolval('E');
float X_current = current_position[X_AXIS],
Y_current = current_position[Y_AXIS];
const float X_probe_location = parser.linearval('X', X_current + X_PROBE_OFFSET_FROM_EXTRUDER),
Y_probe_location = parser.linearval('Y', Y_current + Y_PROBE_OFFSET_FROM_EXTRUDER);
#if DISABLED(DELTA)
if (!WITHIN(X_probe_location, LOGICAL_X_POSITION(MIN_PROBE_X), LOGICAL_X_POSITION(MAX_PROBE_X))) {
out_of_range_error(PSTR("X"));
return;
}
if (!WITHIN(Y_probe_location, LOGICAL_Y_POSITION(MIN_PROBE_Y), LOGICAL_Y_POSITION(MAX_PROBE_Y))) {
out_of_range_error(PSTR("Y"));
return;
}
#else
if (!position_is_reachable_by_probe_xy(X_probe_location, Y_probe_location)) {
SERIAL_PROTOCOLLNPGM("? (X,Y) location outside of probeable radius.");
return;
}
#endif
bool seen_L = parser.seen('L');
uint8_t n_legs = seen_L ? parser.value_byte() : 0;
if (n_legs > 15) {
SERIAL_PROTOCOLLNPGM("?Number of legs in movement not plausible (0-15).");
return;
}
if (n_legs == 1) n_legs = 2;
const bool schizoid_flag = parser.boolval('S');
if (schizoid_flag && !seen_L) n_legs = 7;
/**
* Now get everything to the specified probe point So we can safely do a
* probe to get us close to the bed. If the Z-Axis is far from the bed,
* we don't want to use that as a starting point for each probe.
*/
if (verbose_level > 2)
SERIAL_PROTOCOLLNPGM("Positioning the probe...");
// Disable bed level correction in M48 because we want the raw data when we probe
#if HAS_LEVELING
const bool was_enabled = leveling_is_active();
set_bed_leveling_enabled(false);
#endif
setup_for_endstop_or_probe_move();
double mean = 0.0, sigma = 0.0, min = 99999.9, max = -99999.9, sample_set[n_samples];
// Move to the first point, deploy, and probe
const float t = probe_pt(X_probe_location, Y_probe_location, stow_probe_after_each, verbose_level);
bool probing_good = !isnan(t);
if (probing_good) {
randomSeed(millis());
for (uint8_t n = 0; n < n_samples; n++) {
if (n_legs) {
const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise
float angle = random(0.0, 360.0);
const float radius = random(
#if ENABLED(DELTA)
0.1250000000 * (DELTA_PROBEABLE_RADIUS),
0.3333333333 * (DELTA_PROBEABLE_RADIUS)
#else
5.0, 0.125 * min(X_BED_SIZE, Y_BED_SIZE)
#endif
);
if (verbose_level > 3) {
SERIAL_ECHOPAIR("Starting radius: ", radius);
SERIAL_ECHOPAIR(" angle: ", angle);
SERIAL_ECHOPGM(" Direction: ");
if (dir > 0) SERIAL_ECHOPGM("Counter-");
SERIAL_ECHOLNPGM("Clockwise");
}
for (uint8_t l = 0; l < n_legs - 1; l++) {
double delta_angle;
if (schizoid_flag)
// The points of a 5 point star are 72 degrees apart. We need to
// skip a point and go to the next one on the star.
delta_angle = dir * 2.0 * 72.0;
else
// If we do this line, we are just trying to move further
// around the circle.
delta_angle = dir * (float) random(25, 45);
angle += delta_angle;
while (angle > 360.0) // We probably do not need to keep the angle between 0 and 2*PI, but the
angle -= 360.0; // Arduino documentation says the trig functions should not be given values
while (angle < 0.0) // outside of this range. It looks like they behave correctly with
angle += 360.0; // numbers outside of the range, but just to be safe we clamp them.
X_current = X_probe_location - (X_PROBE_OFFSET_FROM_EXTRUDER) + cos(RADIANS(angle)) * radius;
Y_current = Y_probe_location - (Y_PROBE_OFFSET_FROM_EXTRUDER) + sin(RADIANS(angle)) * radius;
#if DISABLED(DELTA)
X_current = constrain(X_current, X_MIN_POS, X_MAX_POS);
Y_current = constrain(Y_current, Y_MIN_POS, Y_MAX_POS);
#else
// If we have gone out too far, we can do a simple fix and scale the numbers
// back in closer to the origin.
while (!position_is_reachable_by_probe_xy(X_current, Y_current)) {
X_current *= 0.8;
Y_current *= 0.8;
if (verbose_level > 3) {
SERIAL_ECHOPAIR("Pulling point towards center:", X_current);
SERIAL_ECHOLNPAIR(", ", Y_current);
}
}
#endif
if (verbose_level > 3) {
SERIAL_PROTOCOLPGM("Going to:");
SERIAL_ECHOPAIR(" X", X_current);
SERIAL_ECHOPAIR(" Y", Y_current);
SERIAL_ECHOLNPAIR(" Z", current_position[Z_AXIS]);
}
do_blocking_move_to_xy(X_current, Y_current);
} // n_legs loop
} // n_legs
// Probe a single point
sample_set[n] = probe_pt(X_probe_location, Y_probe_location, stow_probe_after_each, 0);
// Break the loop if the probe fails
probing_good = !isnan(sample_set[n]);
if (!probing_good) break;
/**
* Get the current mean for the data points we have so far
*/
double sum = 0.0;
for (uint8_t j = 0; j <= n; j++) sum += sample_set[j];
mean = sum / (n + 1);
NOMORE(min, sample_set[n]);
NOLESS(max, sample_set[n]);
/**
* Now, use that mean to calculate the standard deviation for the
* data points we have so far
*/
sum = 0.0;
for (uint8_t j = 0; j <= n; j++)
sum += sq(sample_set[j] - mean);
sigma = SQRT(sum / (n + 1));
if (verbose_level > 0) {
if (verbose_level > 1) {
SERIAL_PROTOCOL(n + 1);
SERIAL_PROTOCOLPGM(" of ");
SERIAL_PROTOCOL((int)n_samples);
SERIAL_PROTOCOLPGM(": z: ");
SERIAL_PROTOCOL_F(sample_set[n], 3);
if (verbose_level > 2) {
SERIAL_PROTOCOLPGM(" mean: ");
SERIAL_PROTOCOL_F(mean, 4);
SERIAL_PROTOCOLPGM(" sigma: ");
SERIAL_PROTOCOL_F(sigma, 6);
SERIAL_PROTOCOLPGM(" min: ");
SERIAL_PROTOCOL_F(min, 3);
SERIAL_PROTOCOLPGM(" max: ");
SERIAL_PROTOCOL_F(max, 3);
SERIAL_PROTOCOLPGM(" range: ");
SERIAL_PROTOCOL_F(max-min, 3);
}
SERIAL_EOL();
}
}
} // n_samples loop
}
STOW_PROBE();
if (probing_good) {
SERIAL_PROTOCOLLNPGM("Finished!");
if (verbose_level > 0) {
SERIAL_PROTOCOLPGM("Mean: ");
SERIAL_PROTOCOL_F(mean, 6);
SERIAL_PROTOCOLPGM(" Min: ");
SERIAL_PROTOCOL_F(min, 3);
SERIAL_PROTOCOLPGM(" Max: ");
SERIAL_PROTOCOL_F(max, 3);
SERIAL_PROTOCOLPGM(" Range: ");
SERIAL_PROTOCOL_F(max-min, 3);
SERIAL_EOL();
}
SERIAL_PROTOCOLPGM("Standard Deviation: ");
SERIAL_PROTOCOL_F(sigma, 6);
SERIAL_EOL();
SERIAL_EOL();
}
clean_up_after_endstop_or_probe_move();
// Re-enable bed level correction if it had been on
#if HAS_LEVELING
set_bed_leveling_enabled(was_enabled);
#endif
report_current_position();
}

View File

@ -0,0 +1,27 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
void gcode_M49() {
ubl.g26_debug_flag ^= true;
SERIAL_PROTOCOLPGM("UBL Debug Flag turned ");
serialprintPGM(ubl.g26_debug_flag ? PSTR("on.") : PSTR("off."));
}

View File

@ -0,0 +1,93 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#if ENABLED(DELTA)
/**
* M665: Set delta configurations
*
* H = delta height
* L = diagonal rod
* R = delta radius
* S = segments per second
* B = delta calibration radius
* X = Alpha (Tower 1) angle trim
* Y = Beta (Tower 2) angle trim
* Z = Rotate A and B by this angle
*/
void gcode_M665() {
if (parser.seen('H')) {
home_offset[Z_AXIS] = parser.value_linear_units() - DELTA_HEIGHT;
update_software_endstops(Z_AXIS);
}
if (parser.seen('L')) delta_diagonal_rod = parser.value_linear_units();
if (parser.seen('R')) delta_radius = parser.value_linear_units();
if (parser.seen('S')) delta_segments_per_second = parser.value_float();
if (parser.seen('B')) delta_calibration_radius = parser.value_float();
if (parser.seen('X')) delta_tower_angle_trim[A_AXIS] = parser.value_float();
if (parser.seen('Y')) delta_tower_angle_trim[B_AXIS] = parser.value_float();
if (parser.seen('Z')) { // rotate all 3 axis for Z = 0
delta_tower_angle_trim[A_AXIS] -= parser.value_float();
delta_tower_angle_trim[B_AXIS] -= parser.value_float();
}
recalc_delta_settings(delta_radius, delta_diagonal_rod);
}
#elif IS_SCARA
/**
* M665: Set SCARA settings
*
* Parameters:
*
* S[segments-per-second] - Segments-per-second
* P[theta-psi-offset] - Theta-Psi offset, added to the shoulder (A/X) angle
* T[theta-offset] - Theta offset, added to the elbow (B/Y) angle
*
* A, P, and X are all aliases for the shoulder angle
* B, T, and Y are all aliases for the elbow angle
*/
void gcode_M665() {
if (parser.seen('S')) delta_segments_per_second = parser.value_float();
const bool hasA = parser.seen('A'), hasP = parser.seen('P'), hasX = parser.seen('X');
const uint8_t sumAPX = hasA + hasP + hasX;
if (sumAPX == 1)
home_offset[A_AXIS] = parser.value_float();
else if (sumAPX > 1) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM("Only one of A, P, or X is allowed.");
return;
}
const bool hasB = parser.seen('B'), hasT = parser.seen('T'), hasY = parser.seen('Y');
const uint8_t sumBTY = hasB + hasT + hasY;
if (sumBTY == 1)
home_offset[B_AXIS] = parser.value_float();
else if (sumBTY > 1) {
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM("Only one of B, T, or Y is allowed.");
return;
}
}
#endif

View File

@ -0,0 +1,66 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#if ENABLED(DELTA)
/**
* M666: Set delta endstop adjustment
*/
void gcode_M666() {
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) {
SERIAL_ECHOLNPGM(">>> gcode_M666");
}
#endif
LOOP_XYZ(i) {
if (parser.seen(axis_codes[i])) {
endstop_adj[i] = parser.value_linear_units();
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) {
SERIAL_ECHOPAIR("endstop_adj[", axis_codes[i]);
SERIAL_ECHOLNPAIR("] = ", endstop_adj[i]);
}
#endif
}
}
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) {
SERIAL_ECHOLNPGM("<<< gcode_M666");
}
#endif
// normalize endstops so all are <=0; set the residue to delta height
const float z_temp = MAX3(endstop_adj[A_AXIS], endstop_adj[B_AXIS], endstop_adj[C_AXIS]);
home_offset[Z_AXIS] -= z_temp;
LOOP_XYZ(i) endstop_adj[i] -= z_temp;
}
#elif ENABLED(Z_DUAL_ENDSTOPS) // !DELTA && ENABLED(Z_DUAL_ENDSTOPS)
/**
* M666: For Z Dual Endstop setup, set z axis offset to the z2 axis.
*/
void gcode_M666() {
if (parser.seen('Z')) z_endstop_adj = parser.value_linear_units();
SERIAL_ECHOLNPAIR("Z Endstop Adjustment set to (mm):", z_endstop_adj);
}
#endif

View File

@ -0,0 +1,81 @@
/**
* Marlin 3D Printer Firmware
* Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
*
* Based on Sprinter and grbl.
* Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
*
* 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/>.
*
*/
#ifndef CALIBRATE_COMMON_H
#define CALIBRATE_COMMON_H
#if ENABLED(DELTA)
/**
* A delta can only safely home all axes at the same time
* This is like quick_home_xy() but for 3 towers.
*/
inline bool home_delta() {
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS(">>> home_delta", current_position);
#endif
// Init the current position of all carriages to 0,0,0
ZERO(current_position);
sync_plan_position();
// Move all carriages together linearly until an endstop is hit.
current_position[X_AXIS] = current_position[Y_AXIS] = current_position[Z_AXIS] = (DELTA_HEIGHT + home_offset[Z_AXIS] + 10);
feedrate_mm_s = homing_feedrate(X_AXIS);
line_to_current_position();
stepper.synchronize();
// If an endstop was not hit, then damage can occur if homing is continued.
// This can occur if the delta height (DELTA_HEIGHT + home_offset[Z_AXIS]) is
// not set correctly.
if (!(Endstops::endstop_hit_bits & (_BV(X_MAX) | _BV(Y_MAX) | _BV(Z_MAX)))) {
LCD_MESSAGEPGM(MSG_ERR_HOMING_FAILED);
SERIAL_ERROR_START();
SERIAL_ERRORLNPGM(MSG_ERR_HOMING_FAILED);
return false;
}
endstops.hit_on_purpose(); // clear endstop hit flags
// At least one carriage has reached the top.
// Now re-home each carriage separately.
HOMEAXIS(A);
HOMEAXIS(B);
HOMEAXIS(C);
// Set all carriages to their home positions
// Do this here all at once for Delta, because
// XYZ isn't ABC. Applying this per-tower would
// give the impression that they are the same.
LOOP_XYZ(i) set_axis_is_at_home((AxisEnum)i);
SYNC_PLAN_POSITION_KINEMATIC();
#if ENABLED(DEBUG_LEVELING_FEATURE)
if (DEBUGGING(LEVELING)) DEBUG_POS("<<< home_delta", current_position);
#endif
return true;
}
#endif // DELTA
#endif // CALIBRATE_COMMON_H