diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h
index 46d6b96dd10d1c642adc13587a6d624f2baeb22a..070d31c6d4f0ffbc52b34fa7beacb5409dba0816 100644
--- a/Marlin/Configuration.h
+++ b/Marlin/Configuration.h
@@ -376,6 +376,7 @@ const bool Z_MAX_ENDSTOP_INVERTING = true; // set to true to invert the logic of
//============================= Bed Auto Leveling ===========================
//#define ENABLE_AUTO_BED_LEVELING // Delete the comment to enable (remove // at the start of the line)
+#define Z_PROBE_REPEATABILITY_TEST // If not commented out, Z-Probe Repeatability test will be included if Auto Bed Leveling is Enabled.
#ifdef ENABLE_AUTO_BED_LEVELING
diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp
index b1c9e4d3ce5466cd6b4e7d4e1666b6befa6171ba..aa39e53d89bc0e1e88130005897814afc73edb32 100644
--- a/Marlin/Marlin_main.cpp
+++ b/Marlin/Marlin_main.cpp
@@ -894,7 +894,7 @@ static void set_bed_level_equation_lsq(double *plane_equation_coefficients)
current_position[Y_AXIS] = corrected_position.y;
current_position[Z_AXIS] = corrected_position.z;
- // but the bed at 0 so we don't go below it.
+ // put the bed at 0 so we don't go below it.
current_position[Z_AXIS] = zprobe_zoffset; // in the lsq we reach here after raising the extruder due to the loop structure
plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);
@@ -1862,6 +1862,280 @@ void process_commands()
}
}
break;
+
+// M48 Z-Probe repeatability measurement function.
+//
+// Usage: M48 <n #_samples> <X X_position_for_samples> <Y Y_position_for_samples> <V Verbose_Level> <Engage_probe_for_each_reading> <L legs_of_movement_prior_to_doing_probe>
+//
+// This function assumes the bed has been homed. Specificaly, 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.
+//
+// The number of samples will default to 10 if not specified. You can use upper or lower case
+// letters for any of the options EXCEPT n. n must be in lower case because Marlin uses a capital
+// N for its communication protocol and will get horribly confused if you send it a capital N.
+//
+
+#ifdef ENABLE_AUTO_BED_LEVELING
+#ifdef Z_PROBE_REPEATABILITY_TEST
+
+ case 48: // M48 Z-Probe repeatability
+ {
+ #if Z_MIN_PIN == -1
+ #error "You must have a Z_MIN endstop in order to enable calculation of Z-Probe repeatability."
+ #endif
+
+ double sum=0.0;
+ double mean=0.0;
+ double sigma=0.0;
+ double sample_set[50];
+ int verbose_level=1, n=0, j, n_samples = 10, n_legs=0, engage_probe_for_each_reading=0 ;
+ double X_current, Y_current, Z_current;
+ double X_probe_location, Y_probe_location, Z_start_location, ext_position;
+
+ if (code_seen('V') || code_seen('v')) {
+ verbose_level = code_value();
+ if (verbose_level<0 || verbose_level>4 ) {
+ SERIAL_PROTOCOLPGM("?Verbose Level not plausable.\n");
+ goto Sigma_Exit;
+ }
+ }
+
+ if (verbose_level > 0) {
+ SERIAL_PROTOCOLPGM("M48 Z-Probe Repeatability test. Version 2.00\n");
+ SERIAL_PROTOCOLPGM("Full support at: http://3dprintboard.com/forum.php\n");
+ }
+
+ if (code_seen('n')) {
+ n_samples = code_value();
+ if (n_samples<4 || n_samples>50 ) {
+ SERIAL_PROTOCOLPGM("?Specified sample size not plausable.\n");
+ goto Sigma_Exit;
+ }
+ }
+
+ X_current = X_probe_location = st_get_position_mm(X_AXIS);
+ Y_current = Y_probe_location = st_get_position_mm(Y_AXIS);
+ Z_current = st_get_position_mm(Z_AXIS);
+ Z_start_location = st_get_position_mm(Z_AXIS) + Z_RAISE_BEFORE_PROBING;
+ ext_position = st_get_position_mm(E_AXIS);
+
+ if (code_seen('E') || code_seen('e') )
+ engage_probe_for_each_reading++;
+
+ if (code_seen('X') || code_seen('x') ) {
+ X_probe_location = code_value() - X_PROBE_OFFSET_FROM_EXTRUDER;
+ if (X_probe_location<X_MIN_POS || X_probe_location>X_MAX_POS ) {
+ SERIAL_PROTOCOLPGM("?Specified X position out of range.\n");
+ goto Sigma_Exit;
+ }
+ }
+
+ if (code_seen('Y') || code_seen('y') ) {
+ Y_probe_location = code_value() - Y_PROBE_OFFSET_FROM_EXTRUDER;
+ if (Y_probe_location<Y_MIN_POS || Y_probe_location>Y_MAX_POS ) {
+ SERIAL_PROTOCOLPGM("?Specified Y position out of range.\n");
+ goto Sigma_Exit;
+ }
+ }
+
+ if (code_seen('L') || code_seen('l') ) {
+ n_legs = code_value();
+ if ( n_legs==1 )
+ n_legs = 2;
+ if ( n_legs<0 || n_legs>15 ) {
+ SERIAL_PROTOCOLPGM("?Specified number of legs in movement not plausable.\n");
+ goto Sigma_Exit;
+ }
+ }
+
+//
+// Do all the preliminary setup work. First raise the probe.
+//
+
+ st_synchronize();
+ plan_bed_level_matrix.set_to_identity();
+ plan_buffer_line( X_current, Y_current, Z_start_location,
+ ext_position,
+ homing_feedrate[Z_AXIS]/60,
+ active_extruder);
+ st_synchronize();
+
+//
+// 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_PROTOCOL("Positioning probe for the test.\n");
+
+ plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location,
+ ext_position,
+ homing_feedrate[X_AXIS]/60,
+ active_extruder);
+ st_synchronize();
+
+ current_position[X_AXIS] = X_current = st_get_position_mm(X_AXIS);
+ current_position[Y_AXIS] = Y_current = st_get_position_mm(Y_AXIS);
+ current_position[Z_AXIS] = Z_current = st_get_position_mm(Z_AXIS);
+ current_position[E_AXIS] = ext_position = st_get_position_mm(E_AXIS);
+
+//
+// OK, do the inital probe to get us close to the bed.
+// Then retrace the right amount and use that in subsequent probes
+//
+
+ engage_z_probe();
+
+ setup_for_endstop_move();
+ run_z_probe();
+
+ current_position[Z_AXIS] = Z_current = st_get_position_mm(Z_AXIS);
+ Z_start_location = st_get_position_mm(Z_AXIS) + Z_RAISE_BEFORE_PROBING;
+
+ plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location,
+ ext_position,
+ homing_feedrate[X_AXIS]/60,
+ active_extruder);
+ st_synchronize();
+ current_position[Z_AXIS] = Z_current = st_get_position_mm(Z_AXIS);
+
+ if (engage_probe_for_each_reading)
+ retract_z_probe();
+
+ for( n=0; n<n_samples; n++) {
+
+ do_blocking_move_to( X_probe_location, Y_probe_location, Z_start_location); // Make sure we are at the probe location
+
+ if ( n_legs) {
+ double radius=0.0, theta=0.0, x_sweep, y_sweep;
+ int rotational_direction, l;
+
+ rotational_direction = (unsigned long) millis() & 0x0001; // clockwise or counter clockwise
+ radius = (unsigned long) millis() % (long) (X_MAX_LENGTH/4); // limit how far out to go
+ theta = (float) ((unsigned long) millis() % (long) 360) / (360./(2*3.1415926)); // turn into radians
+
+//SERIAL_ECHOPAIR("starting radius: ",radius);
+//SERIAL_ECHOPAIR(" theta: ",theta);
+//SERIAL_ECHOPAIR(" direction: ",rotational_direction);
+//SERIAL_PROTOCOLLNPGM("");
+
+ for( l=0; l<n_legs-1; l++) {
+ if (rotational_direction==1)
+ theta += (float) ((unsigned long) millis() % (long) 20) / (360.0/(2*3.1415926)); // turn into radians
+ else
+ theta -= (float) ((unsigned long) millis() % (long) 20) / (360.0/(2*3.1415926)); // turn into radians
+
+ radius += (float) ( ((long) ((unsigned long) millis() % (long) 10)) - 5);
+ if ( radius<0.0 )
+ radius = -radius;
+
+ X_current = X_probe_location + cos(theta) * radius;
+ Y_current = Y_probe_location + sin(theta) * radius;
+
+ if ( X_current<X_MIN_POS) // Make sure our X & Y are sane
+ X_current = X_MIN_POS;
+ if ( X_current>X_MAX_POS)
+ X_current = X_MAX_POS;
+
+ if ( Y_current<Y_MIN_POS) // Make sure our X & Y are sane
+ Y_current = Y_MIN_POS;
+ if ( Y_current>Y_MAX_POS)
+ Y_current = Y_MAX_POS;
+
+ if (verbose_level>3 ) {
+ SERIAL_ECHOPAIR("x: ", X_current);
+ SERIAL_ECHOPAIR("y: ", Y_current);
+ SERIAL_PROTOCOLLNPGM("");
+ }
+
+ do_blocking_move_to( X_current, Y_current, Z_current );
+ }
+ do_blocking_move_to( X_probe_location, Y_probe_location, Z_start_location); // Go back to the probe location
+ }
+
+ if (engage_probe_for_each_reading) {
+ engage_z_probe();
+ delay(1000);
+ }
+
+ setup_for_endstop_move();
+ run_z_probe();
+
+ sample_set[n] = current_position[Z_AXIS];
+
+//
+// Get the current mean for the data points we have so far
+//
+ sum=0.0;
+ for( j=0; j<=n; j++) {
+ sum = sum + sample_set[j];
+ }
+ mean = sum / (double (n+1));
+//
+// Now, use that mean to calculate the standard deviation for the
+// data points we have so far
+//
+
+ sum=0.0;
+ for( j=0; j<=n; j++) {
+ sum = sum + (sample_set[j]-mean) * (sample_set[j]-mean);
+ }
+ sigma = sqrt( sum / (double (n+1)) );
+
+ if (verbose_level > 1) {
+ SERIAL_PROTOCOL(n+1);
+ SERIAL_PROTOCOL(" of ");
+ SERIAL_PROTOCOL(n_samples);
+ SERIAL_PROTOCOLPGM(" z: ");
+ SERIAL_PROTOCOL_F(current_position[Z_AXIS], 6);
+ }
+
+ if (verbose_level > 2) {
+ SERIAL_PROTOCOL(" mean: ");
+ SERIAL_PROTOCOL_F(mean,6);
+
+ SERIAL_PROTOCOL(" sigma: ");
+ SERIAL_PROTOCOL_F(sigma,6);
+ }
+
+ if (verbose_level > 0)
+ SERIAL_PROTOCOLPGM("\n");
+
+ plan_buffer_line( X_probe_location, Y_probe_location, Z_start_location,
+ current_position[E_AXIS], homing_feedrate[Z_AXIS]/60, active_extruder);
+ st_synchronize();
+
+ if (engage_probe_for_each_reading) {
+ retract_z_probe();
+ delay(1000);
+ }
+ }
+
+ retract_z_probe();
+ delay(1000);
+
+ clean_up_after_endstop_move();
+
+// enable_endstops(true);
+
+ if (verbose_level > 0) {
+ SERIAL_PROTOCOLPGM("Mean: ");
+ SERIAL_PROTOCOL_F(mean, 6);
+ SERIAL_PROTOCOLPGM("\n");
+ }
+
+SERIAL_PROTOCOLPGM("Standard Deviation: ");
+SERIAL_PROTOCOL_F(sigma, 6);
+SERIAL_PROTOCOLPGM("\n\n");
+
+Sigma_Exit:
+ break;
+ }
+#endif // Z_PROBE_REPEATABILITY_TEST
+#endif // ENABLE_AUTO_BED_LEVELING
+
case 104: // M104
if(setTargetedHotend(104)){
break;