|
@@ -27,13 +27,10 @@
|
27
|
27
|
#include "../gcode.h"
|
28
|
28
|
#include "../../module/motion.h"
|
29
|
29
|
#include "../../module/probe.h"
|
|
30
|
+#include "../../lcd/ultralcd.h"
|
30
|
31
|
|
31
|
32
|
#include "../../feature/bedlevel/bedlevel.h"
|
32
|
33
|
|
33
|
|
-#if HAS_SPI_LCD
|
34
|
|
- #include "../../lcd/ultralcd.h"
|
35
|
|
-#endif
|
36
|
|
-
|
37
|
34
|
#if HAS_LEVELING
|
38
|
35
|
#include "../../module/planner.h"
|
39
|
36
|
#endif
|
|
@@ -77,61 +74,85 @@ void GcodeSuite::M48() {
|
77
|
74
|
|
78
|
75
|
const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE;
|
79
|
76
|
|
80
|
|
- xy_float_t next_pos = current_position;
|
81
|
|
-
|
82
|
|
- const xy_pos_t probe_pos = {
|
83
|
|
- parser.linearval('X', next_pos.x + probe.offset_xy.x), // If no X use the probe's current X position
|
84
|
|
- parser.linearval('Y', next_pos.y + probe.offset_xy.y) // If no Y, ditto
|
|
77
|
+ // Test at the current position by default, overridden by X and Y
|
|
78
|
+ const xy_pos_t test_position = {
|
|
79
|
+ parser.linearval('X', current_position.x + probe.offset_xy.x), // If no X use the probe's current X position
|
|
80
|
+ parser.linearval('Y', current_position.y + probe.offset_xy.y) // If no Y, ditto
|
85
|
81
|
};
|
86
|
82
|
|
87
|
|
- if (!probe.can_reach(probe_pos)) {
|
|
83
|
+ if (!probe.can_reach(test_position)) {
|
|
84
|
+ ui.set_status_P(GET_TEXT(MSG_M48_OUT_OF_BOUNDS), 99);
|
88
|
85
|
SERIAL_ECHOLNPGM("? (X,Y) out of bounds.");
|
89
|
86
|
return;
|
90
|
87
|
}
|
91
|
88
|
|
|
89
|
+ // Get the number of leg moves per test-point
|
92
|
90
|
bool seen_L = parser.seen('L');
|
93
|
91
|
uint8_t n_legs = seen_L ? parser.value_byte() : 0;
|
94
|
92
|
if (n_legs > 15) {
|
95
|
|
- SERIAL_ECHOLNPGM("?Number of legs in movement not plausible (0-15).");
|
|
93
|
+ SERIAL_ECHOLNPGM("?Legs of movement implausible (0-15).");
|
96
|
94
|
return;
|
97
|
95
|
}
|
98
|
96
|
if (n_legs == 1) n_legs = 2;
|
99
|
97
|
|
|
98
|
+ // Schizoid motion as an optional stress-test
|
100
|
99
|
const bool schizoid_flag = parser.boolval('S');
|
101
|
100
|
if (schizoid_flag && !seen_L) n_legs = 7;
|
102
|
101
|
|
103
|
|
- /**
|
104
|
|
- * Now get everything to the specified probe point So we can safely do a
|
105
|
|
- * probe to get us close to the bed. If the Z-Axis is far from the bed,
|
106
|
|
- * we don't want to use that as a starting point for each probe.
|
107
|
|
- */
|
108
|
102
|
if (verbose_level > 2)
|
109
|
103
|
SERIAL_ECHOLNPGM("Positioning the probe...");
|
110
|
104
|
|
111
|
|
- // Disable bed level correction in M48 because we want the raw data when we probe
|
|
105
|
+ // Always disable Bed Level correction before probing...
|
112
|
106
|
|
113
|
107
|
#if HAS_LEVELING
|
114
|
108
|
const bool was_enabled = planner.leveling_active;
|
115
|
109
|
set_bed_leveling_enabled(false);
|
116
|
110
|
#endif
|
117
|
111
|
|
|
112
|
+ // Work with reasonable feedrates
|
118
|
113
|
remember_feedrate_scaling_off();
|
119
|
114
|
|
120
|
|
- float mean = 0.0, sigma = 0.0, min = 99999.9, max = -99999.9, sample_set[n_samples];
|
|
115
|
+ // Working variables
|
|
116
|
+ float mean = 0.0, // The average of all points so far, used to calculate deviation
|
|
117
|
+ sigma = 0.0, // Standard deviation of all points so far
|
|
118
|
+ min = 99999.9, // Smallest value sampled so far
|
|
119
|
+ max = -99999.9, // Largest value sampled so far
|
|
120
|
+ sample_set[n_samples]; // Storage for sampled values
|
|
121
|
+
|
|
122
|
+ auto dev_report = [](const bool verbose, const float &mean, const float &sigma, const float &min, const float &max, const bool final=false) {
|
|
123
|
+ if (verbose) {
|
|
124
|
+ SERIAL_ECHOPAIR_F("Mean: ", mean, 6);
|
|
125
|
+ if (!final) SERIAL_ECHOPAIR_F(" Sigma: ", sigma, 6);
|
|
126
|
+ SERIAL_ECHOPAIR_F(" Min: ", min, 3);
|
|
127
|
+ SERIAL_ECHOPAIR_F(" Max: ", max, 3);
|
|
128
|
+ SERIAL_ECHOPAIR_F(" Range: ", max-min, 3);
|
|
129
|
+ if (final) SERIAL_EOL();
|
|
130
|
+ }
|
|
131
|
+ if (final) {
|
|
132
|
+ SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6);
|
|
133
|
+ SERIAL_EOL();
|
|
134
|
+ }
|
|
135
|
+ };
|
121
|
136
|
|
122
|
137
|
// Move to the first point, deploy, and probe
|
123
|
|
- const float t = probe.probe_at_point(probe_pos, raise_after, verbose_level);
|
|
138
|
+ const float t = probe.probe_at_point(test_position, raise_after, verbose_level);
|
124
|
139
|
bool probing_good = !isnan(t);
|
125
|
140
|
|
126
|
141
|
if (probing_good) {
|
127
|
142
|
randomSeed(millis());
|
128
|
143
|
|
|
144
|
+ float sample_sum = 0.0;
|
|
145
|
+
|
129
|
146
|
LOOP_L_N(n, n_samples) {
|
130
|
147
|
#if HAS_SPI_LCD
|
131
|
148
|
// Display M48 progress in the status bar
|
132
|
149
|
ui.status_printf_P(0, PSTR(S_FMT ": %d/%d"), GET_TEXT(MSG_M48_POINT), int(n + 1), int(n_samples));
|
133
|
150
|
#endif
|
|
151
|
+
|
|
152
|
+ // When there are "legs" of movement move around the point before probing
|
134
|
153
|
if (n_legs) {
|
|
154
|
+
|
|
155
|
+ // Pick a random direction, starting angle, and radius
|
135
|
156
|
const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise
|
136
|
157
|
float angle = random(0, 360);
|
137
|
158
|
const float radius = random(
|
|
@@ -142,48 +163,51 @@ void GcodeSuite::M48() {
|
142
|
163
|
int(5), int(0.125 * _MIN(X_BED_SIZE, Y_BED_SIZE))
|
143
|
164
|
#endif
|
144
|
165
|
);
|
145
|
|
-
|
146
|
166
|
if (verbose_level > 3) {
|
147
|
167
|
SERIAL_ECHOPAIR("Start radius:", radius, " angle:", angle, " dir:");
|
148
|
168
|
if (dir > 0) SERIAL_CHAR('C');
|
149
|
169
|
SERIAL_ECHOLNPGM("CW");
|
150
|
170
|
}
|
151
|
171
|
|
|
172
|
+ // Move from leg to leg in rapid succession
|
152
|
173
|
LOOP_L_N(l, n_legs - 1) {
|
153
|
|
- float delta_angle;
|
154
|
174
|
|
|
175
|
+ // Move some distance around the perimeter
|
|
176
|
+ float delta_angle;
|
155
|
177
|
if (schizoid_flag) {
|
156
|
|
- // The points of a 5 point star are 72 degrees apart. We need to
|
157
|
|
- // skip a point and go to the next one on the star.
|
|
178
|
+ // The points of a 5 point star are 72 degrees apart.
|
|
179
|
+ // Skip a point and go to the next one on the star.
|
158
|
180
|
delta_angle = dir * 2.0 * 72.0;
|
159
|
181
|
}
|
160
|
182
|
else {
|
161
|
|
- // If we do this line, we are just trying to move further
|
162
|
|
- // around the circle.
|
163
|
|
- delta_angle = dir * (float) random(25, 45);
|
|
183
|
+ // Just move further along the perimeter.
|
|
184
|
+ delta_angle = dir * (float)random(25, 45);
|
164
|
185
|
}
|
165
|
|
-
|
166
|
186
|
angle += delta_angle;
|
167
|
|
- while (angle > 360.0) angle -= 360.0; // We probably do not need to keep the angle between 0 and 2*PI, but the
|
168
|
|
- // Arduino documentation says the trig functions should not be given values
|
169
|
|
- while (angle < 0.0) angle += 360.0; // outside of this range. It looks like they behave correctly with
|
170
|
|
- // numbers outside of the range, but just to be safe we clamp them.
|
171
|
187
|
|
172
|
|
- const xy_pos_t noz_pos = probe_pos - probe.offset_xy;
|
173
|
|
- next_pos.set(noz_pos.x + cos(RADIANS(angle)) * radius,
|
174
|
|
- noz_pos.y + sin(RADIANS(angle)) * radius);
|
|
188
|
+ // Trig functions work without clamping, but just to be safe...
|
|
189
|
+ while (angle > 360.0) angle -= 360.0;
|
|
190
|
+ while (angle < 0.0) angle += 360.0;
|
175
|
191
|
|
176
|
|
- #if DISABLED(DELTA)
|
177
|
|
- LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS);
|
178
|
|
- LIMIT(next_pos.y, Y_MIN_POS, Y_MAX_POS);
|
179
|
|
- #else
|
180
|
|
- // If we have gone out too far, we can do a simple fix and scale the numbers
|
181
|
|
- // back in closer to the origin.
|
|
192
|
+ // Choose the next position as an offset to chosen test position
|
|
193
|
+ const xy_pos_t noz_pos = test_position - probe.offset_xy;
|
|
194
|
+ xy_pos_t next_pos = {
|
|
195
|
+ noz_pos.x + cos(RADIANS(angle)) * radius,
|
|
196
|
+ noz_pos.y + sin(RADIANS(angle)) * radius
|
|
197
|
+ };
|
|
198
|
+
|
|
199
|
+ #if ENABLED(DELTA)
|
|
200
|
+ // If the probe can't reach the point on a round bed...
|
|
201
|
+ // Simply scale the numbers to bring them closer to origin.
|
182
|
202
|
while (!probe.can_reach(next_pos)) {
|
183
|
203
|
next_pos *= 0.8f;
|
184
|
204
|
if (verbose_level > 3)
|
185
|
205
|
SERIAL_ECHOLNPAIR_P(PSTR("Moving inward: X"), next_pos.x, SP_Y_STR, next_pos.y);
|
186
|
206
|
}
|
|
207
|
+ #else
|
|
208
|
+ // For a rectangular bed just keep the probe in bounds
|
|
209
|
+ LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS);
|
|
210
|
+ LIMIT(next_pos.y, Y_MIN_POS, Y_MAX_POS);
|
187
|
211
|
#endif
|
188
|
212
|
|
189
|
213
|
if (verbose_level > 3)
|
|
@@ -194,45 +218,35 @@ void GcodeSuite::M48() {
|
194
|
218
|
} // n_legs
|
195
|
219
|
|
196
|
220
|
// Probe a single point
|
197
|
|
- sample_set[n] = probe.probe_at_point(probe_pos, raise_after, 0);
|
|
221
|
+ const float pz = probe.probe_at_point(test_position, raise_after, 0);
|
198
|
222
|
|
199
|
223
|
// Break the loop if the probe fails
|
200
|
|
- probing_good = !isnan(sample_set[n]);
|
|
224
|
+ probing_good = !isnan(pz);
|
201
|
225
|
if (!probing_good) break;
|
202
|
226
|
|
203
|
|
- /**
|
204
|
|
- * Get the current mean for the data points we have so far
|
205
|
|
- */
|
206
|
|
- float sum = 0.0;
|
207
|
|
- LOOP_LE_N(j, n) sum += sample_set[j];
|
208
|
|
- mean = sum / (n + 1);
|
209
|
|
-
|
210
|
|
- NOMORE(min, sample_set[n]);
|
211
|
|
- NOLESS(max, sample_set[n]);
|
212
|
|
-
|
213
|
|
- /**
|
214
|
|
- * Now, use that mean to calculate the standard deviation for the
|
215
|
|
- * data points we have so far
|
216
|
|
- */
|
217
|
|
- sum = 0.0;
|
218
|
|
- LOOP_LE_N(j, n)
|
219
|
|
- sum += sq(sample_set[j] - mean);
|
220
|
|
-
|
221
|
|
- sigma = SQRT(sum / (n + 1));
|
222
|
|
- if (verbose_level > 0) {
|
223
|
|
- if (verbose_level > 1) {
|
224
|
|
- SERIAL_ECHO(n + 1);
|
225
|
|
- SERIAL_ECHOPAIR(" of ", int(n_samples));
|
226
|
|
- SERIAL_ECHOPAIR_F(": z: ", sample_set[n], 3);
|
227
|
|
- if (verbose_level > 2) {
|
228
|
|
- SERIAL_ECHOPAIR_F(" mean: ", mean, 4);
|
229
|
|
- SERIAL_ECHOPAIR_F(" sigma: ", sigma, 6);
|
230
|
|
- SERIAL_ECHOPAIR_F(" min: ", min, 3);
|
231
|
|
- SERIAL_ECHOPAIR_F(" max: ", max, 3);
|
232
|
|
- SERIAL_ECHOPAIR_F(" range: ", max-min, 3);
|
233
|
|
- }
|
234
|
|
- SERIAL_EOL();
|
235
|
|
- }
|
|
227
|
+ // Store the new sample
|
|
228
|
+ sample_set[n] = pz;
|
|
229
|
+
|
|
230
|
+ // Keep track of the largest and smallest samples
|
|
231
|
+ NOMORE(min, pz);
|
|
232
|
+ NOLESS(max, pz);
|
|
233
|
+
|
|
234
|
+ // Get the mean value of all samples thus far
|
|
235
|
+ sample_sum += pz;
|
|
236
|
+ mean = sample_sum / (n + 1);
|
|
237
|
+
|
|
238
|
+ // Calculate the standard deviation so far.
|
|
239
|
+ // The value after the last sample will be the final output.
|
|
240
|
+ float dev_sum = 0.0;
|
|
241
|
+ LOOP_LE_N(j, n) dev_sum += sq(sample_set[j] - mean);
|
|
242
|
+ sigma = SQRT(dev_sum / (n + 1));
|
|
243
|
+
|
|
244
|
+ if (verbose_level > 1) {
|
|
245
|
+ SERIAL_ECHO(n + 1);
|
|
246
|
+ SERIAL_ECHOPAIR(" of ", int(n_samples));
|
|
247
|
+ SERIAL_ECHOPAIR_F(": z: ", pz, 3);
|
|
248
|
+ dev_report(verbose_level > 2, mean, sigma, min, max);
|
|
249
|
+ SERIAL_EOL();
|
236
|
250
|
}
|
237
|
251
|
|
238
|
252
|
} // n_samples loop
|
|
@@ -242,16 +256,7 @@ void GcodeSuite::M48() {
|
242
|
256
|
|
243
|
257
|
if (probing_good) {
|
244
|
258
|
SERIAL_ECHOLNPGM("Finished!");
|
245
|
|
-
|
246
|
|
- if (verbose_level > 0) {
|
247
|
|
- SERIAL_ECHOPAIR_F("Mean: ", mean, 6);
|
248
|
|
- SERIAL_ECHOPAIR_F(" Min: ", min, 3);
|
249
|
|
- SERIAL_ECHOPAIR_F(" Max: ", max, 3);
|
250
|
|
- SERIAL_ECHOLNPAIR_F(" Range: ", max-min, 3);
|
251
|
|
- }
|
252
|
|
-
|
253
|
|
- SERIAL_ECHOLNPAIR_F("Standard Deviation: ", sigma, 6);
|
254
|
|
- SERIAL_EOL();
|
|
259
|
+ dev_report(verbose_level > 0, mean, sigma, min, max, true);
|
255
|
260
|
|
256
|
261
|
#if HAS_SPI_LCD
|
257
|
262
|
// Display M48 results in the status bar
|