|
@ -27,13 +27,10 @@ |
|
|
#include "../gcode.h" |
|
|
#include "../gcode.h" |
|
|
#include "../../module/motion.h" |
|
|
#include "../../module/motion.h" |
|
|
#include "../../module/probe.h" |
|
|
#include "../../module/probe.h" |
|
|
|
|
|
#include "../../lcd/ultralcd.h" |
|
|
|
|
|
|
|
|
#include "../../feature/bedlevel/bedlevel.h" |
|
|
#include "../../feature/bedlevel/bedlevel.h" |
|
|
|
|
|
|
|
|
#if HAS_SPI_LCD |
|
|
|
|
|
#include "../../lcd/ultralcd.h" |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#if HAS_LEVELING |
|
|
#if HAS_LEVELING |
|
|
#include "../../module/planner.h" |
|
|
#include "../../module/planner.h" |
|
|
#endif |
|
|
#endif |
|
@ -77,61 +74,85 @@ void GcodeSuite::M48() { |
|
|
|
|
|
|
|
|
const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; |
|
|
const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; |
|
|
|
|
|
|
|
|
xy_float_t next_pos = current_position; |
|
|
// Test at the current position by default, overridden by X and Y
|
|
|
|
|
|
const xy_pos_t test_position = { |
|
|
const xy_pos_t probe_pos = { |
|
|
parser.linearval('X', current_position.x + probe.offset_xy.x), // If no X use the probe's current X position
|
|
|
parser.linearval('X', next_pos.x + probe.offset_xy.x), // If no X use the probe's current X position
|
|
|
parser.linearval('Y', current_position.y + probe.offset_xy.y) // If no Y, ditto
|
|
|
parser.linearval('Y', next_pos.y + probe.offset_xy.y) // If no Y, ditto
|
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
if (!probe.can_reach(probe_pos)) { |
|
|
if (!probe.can_reach(test_position)) { |
|
|
|
|
|
ui.set_status_P(GET_TEXT(MSG_M48_OUT_OF_BOUNDS), 99); |
|
|
SERIAL_ECHOLNPGM("? (X,Y) out of bounds."); |
|
|
SERIAL_ECHOLNPGM("? (X,Y) out of bounds."); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Get the number of leg moves per test-point
|
|
|
bool seen_L = parser.seen('L'); |
|
|
bool seen_L = parser.seen('L'); |
|
|
uint8_t n_legs = seen_L ? parser.value_byte() : 0; |
|
|
uint8_t n_legs = seen_L ? parser.value_byte() : 0; |
|
|
if (n_legs > 15) { |
|
|
if (n_legs > 15) { |
|
|
SERIAL_ECHOLNPGM("?Number of legs in movement not plausible (0-15)."); |
|
|
SERIAL_ECHOLNPGM("?Legs of movement implausible (0-15)."); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
if (n_legs == 1) n_legs = 2; |
|
|
if (n_legs == 1) n_legs = 2; |
|
|
|
|
|
|
|
|
|
|
|
// Schizoid motion as an optional stress-test
|
|
|
const bool schizoid_flag = parser.boolval('S'); |
|
|
const bool schizoid_flag = parser.boolval('S'); |
|
|
if (schizoid_flag && !seen_L) n_legs = 7; |
|
|
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) |
|
|
if (verbose_level > 2) |
|
|
SERIAL_ECHOLNPGM("Positioning the probe..."); |
|
|
SERIAL_ECHOLNPGM("Positioning the probe..."); |
|
|
|
|
|
|
|
|
// Disable bed level correction in M48 because we want the raw data when we probe
|
|
|
// Always disable Bed Level correction before probing...
|
|
|
|
|
|
|
|
|
#if HAS_LEVELING |
|
|
#if HAS_LEVELING |
|
|
const bool was_enabled = planner.leveling_active; |
|
|
const bool was_enabled = planner.leveling_active; |
|
|
set_bed_leveling_enabled(false); |
|
|
set_bed_leveling_enabled(false); |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
// Work with reasonable feedrates
|
|
|
remember_feedrate_scaling_off(); |
|
|
remember_feedrate_scaling_off(); |
|
|
|
|
|
|
|
|
float mean = 0.0, sigma = 0.0, min = 99999.9, max = -99999.9, sample_set[n_samples]; |
|
|
// Working variables
|
|
|
|
|
|
float mean = 0.0, // The average of all points so far, used to calculate deviation
|
|
|
|
|
|
sigma = 0.0, // Standard deviation of all points so far
|
|
|
|
|
|
min = 99999.9, // Smallest value sampled so far
|
|
|
|
|
|
max = -99999.9, // Largest value sampled so far
|
|
|
|
|
|
sample_set[n_samples]; // Storage for sampled values
|
|
|
|
|
|
|
|
|
|
|
|
auto dev_report = [](const bool verbose, const float &mean, const float &sigma, const float &min, const float &max, const bool final=false) { |
|
|
|
|
|
if (verbose) { |
|
|
|
|
|
SERIAL_ECHOPAIR_F("Mean: ", mean, 6); |
|
|
|
|
|
if (!final) SERIAL_ECHOPAIR_F(" Sigma: ", sigma, 6); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" Min: ", min, 3); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" Max: ", max, 3); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" Range: ", max-min, 3); |
|
|
|
|
|
if (final) SERIAL_EOL(); |
|
|
|
|
|
} |
|
|
|
|
|
if (final) { |
|
|
|
|
|
SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6); |
|
|
|
|
|
SERIAL_EOL(); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// Move to the first point, deploy, and probe
|
|
|
// Move to the first point, deploy, and probe
|
|
|
const float t = probe.probe_at_point(probe_pos, raise_after, verbose_level); |
|
|
const float t = probe.probe_at_point(test_position, raise_after, verbose_level); |
|
|
bool probing_good = !isnan(t); |
|
|
bool probing_good = !isnan(t); |
|
|
|
|
|
|
|
|
if (probing_good) { |
|
|
if (probing_good) { |
|
|
randomSeed(millis()); |
|
|
randomSeed(millis()); |
|
|
|
|
|
|
|
|
|
|
|
float sample_sum = 0.0; |
|
|
|
|
|
|
|
|
LOOP_L_N(n, n_samples) { |
|
|
LOOP_L_N(n, n_samples) { |
|
|
#if HAS_SPI_LCD |
|
|
#if HAS_SPI_LCD |
|
|
// Display M48 progress in the status bar
|
|
|
// Display M48 progress in the status bar
|
|
|
ui.status_printf_P(0, PSTR(S_FMT ": %d/%d"), GET_TEXT(MSG_M48_POINT), int(n + 1), int(n_samples)); |
|
|
ui.status_printf_P(0, PSTR(S_FMT ": %d/%d"), GET_TEXT(MSG_M48_POINT), int(n + 1), int(n_samples)); |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
// When there are "legs" of movement move around the point before probing
|
|
|
if (n_legs) { |
|
|
if (n_legs) { |
|
|
|
|
|
|
|
|
|
|
|
// Pick a random direction, starting angle, and radius
|
|
|
const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise
|
|
|
const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise
|
|
|
float angle = random(0, 360); |
|
|
float angle = random(0, 360); |
|
|
const float radius = random( |
|
|
const float radius = random( |
|
@ -142,48 +163,51 @@ void GcodeSuite::M48() { |
|
|
int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE)) |
|
|
int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE)) |
|
|
#endif |
|
|
#endif |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (verbose_level > 3) { |
|
|
if (verbose_level > 3) { |
|
|
SERIAL_ECHOPAIR("Start radius:", radius, " angle:", angle, " dir:"); |
|
|
SERIAL_ECHOPAIR("Start radius:", radius, " angle:", angle, " dir:"); |
|
|
if (dir > 0) SERIAL_CHAR('C'); |
|
|
if (dir > 0) SERIAL_CHAR('C'); |
|
|
SERIAL_ECHOLNPGM("CW"); |
|
|
SERIAL_ECHOLNPGM("CW"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Move from leg to leg in rapid succession
|
|
|
LOOP_L_N(l, n_legs - 1) { |
|
|
LOOP_L_N(l, n_legs - 1) { |
|
|
float delta_angle; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Move some distance around the perimeter
|
|
|
|
|
|
float delta_angle; |
|
|
if (schizoid_flag) { |
|
|
if (schizoid_flag) { |
|
|
// The points of a 5 point star are 72 degrees apart. We need to
|
|
|
// The points of a 5 point star are 72 degrees apart.
|
|
|
// skip a point and go to the next one on the star.
|
|
|
// Skip a point and go to the next one on the star.
|
|
|
delta_angle = dir * 2.0 * 72.0; |
|
|
delta_angle = dir * 2.0 * 72.0; |
|
|
} |
|
|
} |
|
|
else { |
|
|
else { |
|
|
// If we do this line, we are just trying to move further
|
|
|
// Just move further along the perimeter.
|
|
|
// around the circle.
|
|
|
|
|
|
delta_angle = dir * (float)random(25, 45); |
|
|
delta_angle = dir * (float)random(25, 45); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
angle += delta_angle; |
|
|
angle += delta_angle; |
|
|
while (angle > 360.0) angle -= 360.0; // We probably do not need to keep the angle between 0 and 2*PI, but the
|
|
|
|
|
|
// Arduino documentation says the trig functions should not be given values
|
|
|
|
|
|
while (angle < 0.0) angle += 360.0; // outside of this range. It looks like they behave correctly with
|
|
|
|
|
|
// numbers outside of the range, but just to be safe we clamp them.
|
|
|
|
|
|
|
|
|
|
|
|
const xy_pos_t noz_pos = probe_pos - probe.offset_xy; |
|
|
// Trig functions work without clamping, but just to be safe...
|
|
|
next_pos.set(noz_pos.x + cos(RADIANS(angle)) * radius, |
|
|
while (angle > 360.0) angle -= 360.0; |
|
|
noz_pos.y + sin(RADIANS(angle)) * radius); |
|
|
while (angle < 0.0) angle += 360.0; |
|
|
|
|
|
|
|
|
#if DISABLED(DELTA) |
|
|
// Choose the next position as an offset to chosen test position
|
|
|
LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS); |
|
|
const xy_pos_t noz_pos = test_position - probe.offset_xy; |
|
|
LIMIT(next_pos.y, Y_MIN_POS, Y_MAX_POS); |
|
|
xy_pos_t next_pos = { |
|
|
#else |
|
|
noz_pos.x + cos(RADIANS(angle)) * radius, |
|
|
// If we have gone out too far, we can do a simple fix and scale the numbers
|
|
|
noz_pos.y + sin(RADIANS(angle)) * radius |
|
|
// back in closer to the origin.
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
#if ENABLED(DELTA) |
|
|
|
|
|
// If the probe can't reach the point on a round bed...
|
|
|
|
|
|
// Simply scale the numbers to bring them closer to origin.
|
|
|
while (!probe.can_reach(next_pos)) { |
|
|
while (!probe.can_reach(next_pos)) { |
|
|
next_pos *= 0.8f; |
|
|
next_pos *= 0.8f; |
|
|
if (verbose_level > 3) |
|
|
if (verbose_level > 3) |
|
|
SERIAL_ECHOLNPAIR_P(PSTR("Moving inward: X"), next_pos.x, SP_Y_STR, next_pos.y); |
|
|
SERIAL_ECHOLNPAIR_P(PSTR("Moving inward: X"), next_pos.x, SP_Y_STR, next_pos.y); |
|
|
} |
|
|
} |
|
|
|
|
|
#else |
|
|
|
|
|
// For a rectangular bed just keep the probe in bounds
|
|
|
|
|
|
LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS); |
|
|
|
|
|
LIMIT(next_pos.y, Y_MIN_POS, Y_MAX_POS); |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
|
|
|
if (verbose_level > 3) |
|
|
if (verbose_level > 3) |
|
@ -194,46 +218,36 @@ void GcodeSuite::M48() { |
|
|
} // n_legs
|
|
|
} // n_legs
|
|
|
|
|
|
|
|
|
// Probe a single point
|
|
|
// Probe a single point
|
|
|
sample_set[n] = probe.probe_at_point(probe_pos, raise_after, 0); |
|
|
const float pz = probe.probe_at_point(test_position, raise_after, 0); |
|
|
|
|
|
|
|
|
// Break the loop if the probe fails
|
|
|
// Break the loop if the probe fails
|
|
|
probing_good = !isnan(sample_set[n]); |
|
|
probing_good = !isnan(pz); |
|
|
if (!probing_good) break; |
|
|
if (!probing_good) break; |
|
|
|
|
|
|
|
|
/**
|
|
|
// Store the new sample
|
|
|
* Get the current mean for the data points we have so far |
|
|
sample_set[n] = pz; |
|
|
*/ |
|
|
|
|
|
float sum = 0.0; |
|
|
|
|
|
LOOP_LE_N(j, n) sum += sample_set[j]; |
|
|
|
|
|
mean = sum / (n + 1); |
|
|
|
|
|
|
|
|
|
|
|
NOMORE(min, sample_set[n]); |
|
|
// Keep track of the largest and smallest samples
|
|
|
NOLESS(max, sample_set[n]); |
|
|
NOMORE(min, pz); |
|
|
|
|
|
NOLESS(max, pz); |
|
|
|
|
|
|
|
|
/**
|
|
|
// Get the mean value of all samples thus far
|
|
|
* Now, use that mean to calculate the standard deviation for the |
|
|
sample_sum += pz; |
|
|
* data points we have so far |
|
|
mean = sample_sum / (n + 1); |
|
|
*/ |
|
|
|
|
|
sum = 0.0; |
|
|
// Calculate the standard deviation so far.
|
|
|
LOOP_LE_N(j, n) |
|
|
// The value after the last sample will be the final output.
|
|
|
sum += sq(sample_set[j] - mean); |
|
|
float dev_sum = 0.0; |
|
|
|
|
|
LOOP_LE_N(j, n) dev_sum += sq(sample_set[j] - mean); |
|
|
|
|
|
sigma = SQRT(dev_sum / (n + 1)); |
|
|
|
|
|
|
|
|
sigma = SQRT(sum / (n + 1)); |
|
|
|
|
|
if (verbose_level > 0) { |
|
|
|
|
|
if (verbose_level > 1) { |
|
|
if (verbose_level > 1) { |
|
|
SERIAL_ECHO(n + 1); |
|
|
SERIAL_ECHO(n + 1); |
|
|
SERIAL_ECHOPAIR(" of ", int(n_samples)); |
|
|
SERIAL_ECHOPAIR(" of ", int(n_samples)); |
|
|
SERIAL_ECHOPAIR_F(": z: ", sample_set[n], 3); |
|
|
SERIAL_ECHOPAIR_F(": z: ", pz, 3); |
|
|
if (verbose_level > 2) { |
|
|
dev_report(verbose_level > 2, mean, sigma, min, max); |
|
|
SERIAL_ECHOPAIR_F(" mean: ", mean, 4); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" sigma: ", sigma, 6); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" min: ", min, 3); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" max: ", max, 3); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" range: ", max-min, 3); |
|
|
|
|
|
} |
|
|
|
|
|
SERIAL_EOL(); |
|
|
SERIAL_EOL(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} // n_samples loop
|
|
|
} // n_samples loop
|
|
|
} |
|
|
} |
|
@ -242,16 +256,7 @@ void GcodeSuite::M48() { |
|
|
|
|
|
|
|
|
if (probing_good) { |
|
|
if (probing_good) { |
|
|
SERIAL_ECHOLNPGM("Finished!"); |
|
|
SERIAL_ECHOLNPGM("Finished!"); |
|
|
|
|
|
dev_report(verbose_level > 0, mean, sigma, min, max, true); |
|
|
if (verbose_level > 0) { |
|
|
|
|
|
SERIAL_ECHOPAIR_F("Mean: ", mean, 6); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" Min: ", min, 3); |
|
|
|
|
|
SERIAL_ECHOPAIR_F(" Max: ", max, 3); |
|
|
|
|
|
SERIAL_ECHOLNPAIR_F(" Range: ", max-min, 3); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6); |
|
|
|
|
|
SERIAL_EOL(); |
|
|
|
|
|
|
|
|
|
|
|
#if HAS_SPI_LCD |
|
|
#if HAS_SPI_LCD |
|
|
// Display M48 results in the status bar
|
|
|
// Display M48 results in the status bar
|
|
|