Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Repeatability Test from LCD to test in place #19104

Merged
merged 11 commits into from
Aug 22, 2020
175 changes: 90 additions & 85 deletions Marlin/src/gcode/calibrate/M48.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,10 @@
#include "../gcode.h"
#include "../../module/motion.h"
#include "../../module/probe.h"
#include "../../lcd/ultralcd.h"

#include "../../feature/bedlevel/bedlevel.h"

#if HAS_SPI_LCD
#include "../../lcd/ultralcd.h"
#endif

#if HAS_LEVELING
#include "../../module/planner.h"
#endif
Expand Down Expand Up @@ -77,61 +74,85 @@ void GcodeSuite::M48() {

const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE;

xy_float_t next_pos = current_position;

const xy_pos_t probe_pos = {
parser.linearval('X', next_pos.x + probe.offset_xy.x), // If no X use the probe's current X position
parser.linearval('Y', next_pos.y + probe.offset_xy.y) // If no Y, ditto
// Test at the current position by default, overridden by X and Y
const xy_pos_t test_position = {
parser.linearval('X', current_position.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
};

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.");
return;
}

// Get the number of leg moves per test-point
bool seen_L = parser.seen('L');
uint8_t n_legs = seen_L ? parser.value_byte() : 0;
if (n_legs > 15) {
SERIAL_ECHOLNPGM("?Number of legs in movement not plausible (0-15).");
SERIAL_ECHOLNPGM("?Legs of movement implausible (0-15).");
return;
}
if (n_legs == 1) n_legs = 2;

// Schizoid motion as an optional stress-test
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_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
const bool was_enabled = planner.leveling_active;
set_bed_leveling_enabled(false);
#endif

// Work with reasonable feedrates
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
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);

if (probing_good) {
randomSeed(millis());

float sample_sum = 0.0;

LOOP_L_N(n, n_samples) {
#if HAS_SPI_LCD
// 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));
#endif

// When there are "legs" of movement move around the point before probing
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
float angle = random(0, 360);
const float radius = random(
Expand All @@ -142,48 +163,51 @@ void GcodeSuite::M48() {
int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE))
#endif
);

if (verbose_level > 3) {
SERIAL_ECHOPAIR("Start radius:", radius, " angle:", angle, " dir:");
if (dir > 0) SERIAL_CHAR('C');
SERIAL_ECHOLNPGM("CW");
}

// Move from leg to leg in rapid succession
LOOP_L_N(l, n_legs - 1) {
float delta_angle;

// Move some distance around the perimeter
float 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.
// The points of a 5 point star are 72 degrees apart.
// 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);
// Just move further along the perimeter.
delta_angle = dir * (float)random(25, 45);
}

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;
next_pos.set(noz_pos.x + cos(RADIANS(angle)) * radius,
noz_pos.y + sin(RADIANS(angle)) * radius);
// Trig functions work without clamping, but just to be safe...
while (angle > 360.0) angle -= 360.0;
while (angle < 0.0) angle += 360.0;

#if DISABLED(DELTA)
LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS);
LIMIT(next_pos.y, 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.
// Choose the next position as an offset to chosen test position
const xy_pos_t noz_pos = test_position - probe.offset_xy;
xy_pos_t next_pos = {
noz_pos.x + cos(RADIANS(angle)) * radius,
noz_pos.y + sin(RADIANS(angle)) * radius
};

#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)) {
next_pos *= 0.8f;
if (verbose_level > 3)
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

if (verbose_level > 3)
Expand All @@ -194,45 +218,35 @@ void GcodeSuite::M48() {
} // n_legs

// 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
probing_good = !isnan(sample_set[n]);
probing_good = !isnan(pz);
if (!probing_good) break;

/**
* Get the current mean for the data points we have so far
*/
float sum = 0.0;
LOOP_LE_N(j, n) 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;
LOOP_LE_N(j, n)
sum += sq(sample_set[j] - mean);

sigma = SQRT(sum / (n + 1));
if (verbose_level > 0) {
if (verbose_level > 1) {
SERIAL_ECHO(n + 1);
SERIAL_ECHOPAIR(" of ", int(n_samples));
SERIAL_ECHOPAIR_F(": z: ", sample_set[n], 3);
if (verbose_level > 2) {
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();
}
// Store the new sample
sample_set[n] = pz;

// Keep track of the largest and smallest samples
NOMORE(min, pz);
NOLESS(max, pz);

// Get the mean value of all samples thus far
sample_sum += pz;
mean = sample_sum / (n + 1);

// Calculate the standard deviation so far.
// The value after the last sample will be the final output.
float dev_sum = 0.0;
LOOP_LE_N(j, n) dev_sum += sq(sample_set[j] - mean);
sigma = SQRT(dev_sum / (n + 1));

if (verbose_level > 1) {
SERIAL_ECHO(n + 1);
SERIAL_ECHOPAIR(" of ", int(n_samples));
SERIAL_ECHOPAIR_F(": z: ", pz, 3);
dev_report(verbose_level > 2, mean, sigma, min, max);
SERIAL_EOL();
}

} // n_samples loop
Expand All @@ -242,16 +256,7 @@ void GcodeSuite::M48() {

if (probing_good) {
SERIAL_ECHOLNPGM("Finished!");

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();
dev_report(verbose_level > 0, mean, sigma, min, max, true);

#if HAS_SPI_LCD
// Display M48 results in the status bar
Expand Down
1 change: 1 addition & 0 deletions Marlin/src/lcd/language/language_en.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ namespace Language_en {
PROGMEM Language_Str MSG_USER_MENU = _UxGT("Custom Commands");
PROGMEM Language_Str MSG_M48_TEST = _UxGT("M48 Probe Test");
PROGMEM Language_Str MSG_M48_POINT = _UxGT("M48 Point");
PROGMEM Language_Str MSG_M48_OUT_OF_BOUNDS = _UxGT("Probe out of bounds");
PROGMEM Language_Str MSG_M48_DEVIATION = _UxGT("Deviation");
PROGMEM Language_Str MSG_IDEX_MENU = _UxGT("IDEX Mode");
PROGMEM Language_Str MSG_OFFSETS_MENU = _UxGT("Tool Offsets");
Expand Down
1 change: 1 addition & 0 deletions Marlin/src/lcd/language/language_it.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ namespace Language_it {
PROGMEM Language_Str MSG_LCD_TILTING_MESH = _UxGT("Punto inclinaz.");
PROGMEM Language_Str MSG_M48_TEST = _UxGT("Test sonda M48");
PROGMEM Language_Str MSG_M48_POINT = _UxGT("Punto M48");
PROGMEM Language_Str MSG_M48_OUT_OF_BOUNDS = _UxGT("Sonda oltre i limiti");
PROGMEM Language_Str MSG_M48_DEVIATION = _UxGT("Deviazione");
PROGMEM Language_Str MSG_IDEX_MENU = _UxGT("Modo IDEX");
PROGMEM Language_Str MSG_OFFSETS_MENU = _UxGT("Strumenti Offsets");
Expand Down
5 changes: 5 additions & 0 deletions Marlin/src/lcd/menu/menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ void menu_move();
//////// Menu Item Helper Functions ////////
////////////////////////////////////////////

void lcd_move_z();
void _lcd_draw_homing();

#define HAS_LINE_TO_Z ANY(DELTA, PROBE_MANUALLY, MESH_BED_LEVELING, LEVEL_BED_CORNERS)
Expand Down Expand Up @@ -230,3 +231,7 @@ void _lcd_draw_homing();
#endif

#endif

#if ENABLED(TOUCH_SCREEN_CALIBRATION)
void touch_screen_calibration();
#endif
2 changes: 1 addition & 1 deletion Marlin/src/lcd/menu/menu_motion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ void menu_motion() {
#endif

#if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
GCODES_ITEM(MSG_M48_TEST, PSTR("G28\nM48 P10"));
GCODES_ITEM(MSG_M48_TEST, PSTR("G28 O\nM48 P10"));
#endif

//
Expand Down
2 changes: 1 addition & 1 deletion Marlin/src/lcd/tft/touch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#include "touch.h"

#include "../ultralcd.h" // for ui methods
#include "../menu/menu.h" // for touch_screen_calibration
#include "../menu/menu_item.h" // for touch_screen_calibration

#include "../../module/temperature.h"
#include "../../module/planner.h"
Expand Down