|  |  |  | @@ -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 | 
		
	
	
		
			
				
					
					|  |  |  | @@ -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( | 
		
	
	
		
			
				
					
					|  |  |  | @@ -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) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -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); | 
		
	
		
			
				|  |  |  |  |       // Store the new sample | 
		
	
		
			
				|  |  |  |  |       sample_set[n] = pz; | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |       NOMORE(min, sample_set[n]); | 
		
	
		
			
				|  |  |  |  |       NOLESS(max, sample_set[n]); | 
		
	
		
			
				|  |  |  |  |       // Keep track of the largest and smallest samples | 
		
	
		
			
				|  |  |  |  |       NOMORE(min, pz); | 
		
	
		
			
				|  |  |  |  |       NOLESS(max, pz); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |       /** | 
		
	
		
			
				|  |  |  |  |        * 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); | 
		
	
		
			
				|  |  |  |  |       // Get the mean value of all samples thus far | 
		
	
		
			
				|  |  |  |  |       sample_sum += pz; | 
		
	
		
			
				|  |  |  |  |       mean = sample_sum / (n + 1); | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |       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(); | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       // 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 | 
		
	
	
		
			
				
					
					|  |  |  | @@ -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 | 
		
	
	
		
			
				
					
					|  |  |  |   |