Implement endstops using new motion control
[clinton/Smoothieware.git] / src / modules / tools / zprobe / DeltaGridStrategy.cpp
1 /*
2 This code is derived from (and mostly copied from) Johann Rocholls code at https://github.com/jcrocholl/Marlin/blob/deltabot/Marlin/Marlin_main.cpp
3 license is the same as his code.
4
5 Summary
6 -------
7 Probes grid_size points in X and Y (total probes grid_size * grid_size) and stores the relative offsets from the 0,0 Z height
8 When enabled every move will calculate the Z offset based on interpolating the height offset within the grids nearest 4 points.
9
10 Configuration
11 -------------
12 The strategy must be enabled in the config as well as zprobe.
13
14 leveling-strategy.delta-grid.enable true
15
16 The radius of the bed must be specified with...
17
18 leveling-strategy.delta-grid.radius 50
19
20 this needs to be at least as big as the maximum printing radius as moves outside of this will not be compensated for correctly
21
22 The size of the grid can be set with...
23
24 leveling-strategy.delta-grid.size 7
25
26 this is the X and Y size of the grid, it must be an odd number, the default is 7 which is 49 probe points
27
28 Optionally probe offsets from the nozzle or tool head can be defined with...
29
30 leveling-strategy.delta-grid.probe_offsets 0,0,0 # probe offsetrs x,y,z
31
32 they may also be set with M565 X0 Y0 Z0
33
34 If the saved grid is to be loaded on boot then this must be set in the config...
35
36 leveling-strategy.delta-grid.save true
37
38 Then when M500 is issued it will save M375 which will cause the grid to be loaded on boot. The default is to not autoload the grid on boot
39
40 Optionally an initial_height can be set that tell the intial probe where to stop the fast decent before it probes, this should be around 5-10mm above the bed
41 leveling-strategy.delta-grid.initial_height 10
42
43
44 Usage
45 -----
46 G29 test probes in a spiral pattern within the radius producing a map of offsets, this can be imported into a graphing program to visualize the bed heights
47 optional parameters {{In}} sets the number of points to the value n, {{Jn}} sets the radius for this probe.
48
49 G31 probes the grid and turns the compensation on, this will remain in effect until reset or M561/M370
50 optional parameters {{Jn}} sets the radius for this probe, which gets saved with M375
51
52 M370 clears the grid and turns off compensation
53 M374 Save grid to /sd/delta.grid
54 M374.1 delete /sd/delta.grid
55 M375 Load the grid from /sd/delta.grid and enable compensation
56 M375.1 display the current grid
57 M561 clears the grid and turns off compensation
58 M565 defines the probe offsets from the nozzle or tool head
59
60
61 M500 saves the probe points
62 M503 displays the current settings
63 */
64
65 #include "DeltaGridStrategy.h"
66
67 #include "Kernel.h"
68 #include "Config.h"
69 #include "Robot.h"
70 #include "StreamOutputPool.h"
71 #include "Gcode.h"
72 #include "checksumm.h"
73 #include "ConfigValue.h"
74 #include "PublicDataRequest.h"
75 #include "PublicData.h"
76 #include "Conveyor.h"
77 #include "ZProbe.h"
78 #include "nuts_bolts.h"
79 #include "utils.h"
80 #include "platform_memory.h"
81
82 #include <string>
83 #include <algorithm>
84 #include <cstdlib>
85 #include <cmath>
86
87 #define grid_radius_checksum CHECKSUM("radius")
88 #define grid_size_checksum CHECKSUM("size")
89 #define tolerance_checksum CHECKSUM("tolerance")
90 #define save_checksum CHECKSUM("save")
91 #define probe_offsets_checksum CHECKSUM("probe_offsets")
92 #define initial_height_checksum CHECKSUM("initial_height")
93
94 #define GRIDFILE "/sd/delta.grid"
95
96 DeltaGridStrategy::DeltaGridStrategy(ZProbe *zprobe) : LevelingStrategy(zprobe)
97 {
98 grid= nullptr;
99 }
100
101 DeltaGridStrategy::~DeltaGridStrategy()
102 {
103 if(grid != nullptr) AHB0.dealloc(grid);
104 }
105
106 bool DeltaGridStrategy::handleConfig()
107 {
108 grid_radius = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, grid_radius_checksum)->by_default(50.0F)->as_number();
109 grid_size = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, grid_size_checksum)->by_default(7)->as_number();
110 tolerance = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, tolerance_checksum)->by_default(0.03F)->as_number();
111 save = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, save_checksum)->by_default(false)->as_bool();
112
113 // the initial height above the bed we stop the intial move down after home to find the bed
114 // this should be a height that is enough that the probe will not hit the bed and is an offset from max_z (can be set to 0 if max_z takes into account the probe offset)
115 this->initial_height = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, initial_height_checksum)->by_default(10)->as_number();
116
117 // Probe offsets xxx,yyy,zzz
118 {
119 std::string po = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, probe_offsets_checksum)->by_default("0,0,0")->as_string();
120 std::vector<float> v = parse_number_list(po.c_str());
121 if(v.size() >= 3) {
122 this->probe_offsets = std::make_tuple(v[0], v[1], v[2]);
123 }
124 }
125
126 // allocate in AHB0
127 grid= (float *)AHB0.alloc(grid_size * grid_size * sizeof(float));
128
129 reset_bed_level();
130
131 return true;
132 }
133
134 void DeltaGridStrategy::save_grid(StreamOutput *stream)
135 {
136 if(isnan(grid[0])) {
137 stream->printf("error:No grid to save\n");
138 return;
139 }
140
141 FILE *fp = fopen(GRIDFILE, "w");
142 if(fp == NULL) {
143 stream->printf("error:Failed to open grid file %s\n", GRIDFILE);
144 return;
145 }
146
147 if(fwrite(&grid_size, sizeof(uint8_t), 1, fp) != 1) {
148 stream->printf("error:Failed to write grid size\n");
149 fclose(fp);
150 return;
151 }
152
153 if(fwrite(&grid_radius, sizeof(float), 1, fp) != 1) {
154 stream->printf("error:Failed to write grid radius\n");
155 fclose(fp);
156 return;
157 }
158
159 for (int y = 0; y < grid_size; y++) {
160 for (int x = 0; x < grid_size; x++) {
161 if(fwrite(&grid[x + (grid_size*y)], sizeof(float), 1, fp) != 1) {
162 stream->printf("error:Failed to write grid\n");
163 fclose(fp);
164 return;
165 }
166 }
167 }
168 stream->printf("grid saved to %s\n", GRIDFILE);
169 fclose(fp);
170 }
171
172 bool DeltaGridStrategy::load_grid(StreamOutput *stream)
173 {
174 FILE *fp = fopen(GRIDFILE, "r");
175 if(fp == NULL) {
176 stream->printf("error:Failed to open grid %s\n", GRIDFILE);
177 return false;
178 }
179
180 uint8_t size;
181 float radius;
182
183 if(fread(&size, sizeof(uint8_t), 1, fp) != 1) {
184 stream->printf("error:Failed to read grid size\n");
185 fclose(fp);
186 return false;
187 }
188
189 if(size != grid_size) {
190 stream->printf("error:grid size is different read %d - config %d\n", size, grid_size);
191 fclose(fp);
192 return false;
193 }
194
195 if(fread(&radius, sizeof(float), 1, fp) != 1) {
196 stream->printf("error:Failed to read grid radius\n");
197 fclose(fp);
198 return false;
199 }
200
201 if(radius != grid_radius) {
202 stream->printf("warning:grid radius is different read %f - config %f, overriding config\n", radius, grid_radius);
203 grid_radius= radius;
204 }
205
206 for (int y = 0; y < grid_size; y++) {
207 for (int x = 0; x < grid_size; x++) {
208 if(fread(&grid[x + (grid_size*y)], sizeof(float), 1, fp) != 1) {
209 stream->printf("error:Failed to read grid\n");
210 fclose(fp);
211 return false;
212 }
213 }
214 }
215 stream->printf("grid loaded, radius: %f, size: %d\n", grid_radius, grid_size);
216 fclose(fp);
217 return true;
218 }
219
220 bool DeltaGridStrategy::probe_grid(int n, float radius, StreamOutput *stream)
221 {
222 if(n < 5) {
223 stream->printf("Need at least a 5x5 grid to probe\n");
224 return true;
225 }
226
227 float initial_z = findBed();
228 if(isnan(initial_z)) return false;
229
230 float d= ((radius*2) / (n - 1));
231
232 for (int c = 0; c < n; ++c) {
233 float y = -radius + d*c;
234 for (int r = 0; r < n; ++r) {
235 float x = -radius + d*r;
236 // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
237 float distance_from_center = sqrtf(x*x + y*y);
238 float z= 0.0F;
239 if (distance_from_center <= radius) {
240 int s;
241 if(!zprobe->doProbeAt(s, x, y)) return false;
242 z = zprobe->getProbeHeight() - zprobe->zsteps_to_mm(s);
243 }
244 stream->printf("%8.4f ", z);
245 }
246 stream->printf("\n");
247 }
248 return true;
249 }
250
251 // taken from Oskars PR #713
252 bool DeltaGridStrategy::probe_spiral(int n, float radius, StreamOutput *stream)
253 {
254 float a = radius / (2 * sqrtf(n * M_PI));
255 float step_length = radius * radius / (2 * a * n);
256
257 float initial_z = findBed();
258 if(isnan(initial_z)) return false;
259
260 auto theta = [a](float length) {return sqrtf(2*length/a); };
261
262 float maxz= NAN, minz= NAN;
263 for (int i = 0; i < n; i++) {
264 float angle = theta(i * step_length);
265 float r = angle * a;
266 // polar to cartesian
267 float x = r * cosf(angle);
268 float y = r * sinf(angle);
269
270 int steps;
271 if (!zprobe->doProbeAt(steps, x, y)) return false;
272 float z = zprobe->getProbeHeight() - zprobe->zsteps_to_mm(steps);
273 stream->printf("PROBE: X%1.4f, Y%1.4f, Z%1.4f\n", x, y, z);
274 if(isnan(maxz) || z > maxz) maxz= z;
275 if(isnan(minz) || z < minz) minz= z;
276 }
277
278 stream->printf("max: %1.4f, min: %1.4f, delta: %1.4f\n", maxz, minz, maxz-minz);
279 return true;
280 }
281
282 bool DeltaGridStrategy::handleGcode(Gcode *gcode)
283 {
284 if(gcode->has_g) {
285 if (gcode->g == 29) { // do a probe to test flatness
286 // first wait for an empty queue i.e. no moves left
287 THEKERNEL->conveyor->wait_for_empty_queue();
288
289 int n= gcode->has_letter('I') ? gcode->get_value('I') : 0;
290 float radius = grid_radius;
291 if(gcode->has_letter('J')) radius = gcode->get_value('J'); // override default probe radius
292 if(gcode->subcode == 1){
293 if(n==0) n= 50;
294 probe_spiral(n, radius, gcode->stream);
295 }else{
296 if(n==0) n= 7;
297 probe_grid(n, radius, gcode->stream);
298 }
299
300 return true;
301
302 } else if( gcode->g == 31 ) { // do a grid probe
303 // first wait for an empty queue i.e. no moves left
304 THEKERNEL->conveyor->wait_for_empty_queue();
305
306 if(!doProbe(gcode)) {
307 gcode->stream->printf("Probe failed to complete, check the initial probe height and/or initial_height settings\n");
308 } else {
309 gcode->stream->printf("Probe completed\n");
310 }
311 return true;
312 }
313
314 } else if(gcode->has_m) {
315 if(gcode->m == 370 || gcode->m == 561) { // M370: Clear bed, M561: Set Identity Transform
316 // delete the compensationTransform in robot
317 setAdjustFunction(false);
318 reset_bed_level();
319 gcode->stream->printf("grid cleared and disabled\n");
320 return true;
321
322 } else if(gcode->m == 374) { // M374: Save grid, M374.1: delete saved grid
323 if(gcode->subcode == 1) {
324 remove(GRIDFILE);
325 gcode->stream->printf("%s deleted\n", GRIDFILE);
326 } else {
327 save_grid(gcode->stream);
328 }
329
330 return true;
331
332 } else if(gcode->m == 375) { // M375: load grid, M375.1 display grid
333 if(gcode->subcode == 1) {
334 print_bed_level(gcode->stream);
335 } else {
336 if(load_grid(gcode->stream)) setAdjustFunction(true);
337 }
338 return true;
339
340 } else if(gcode->m == 565) { // M565: Set Z probe offsets
341 float x = 0, y = 0, z = 0;
342 if(gcode->has_letter('X')) x = gcode->get_value('X');
343 if(gcode->has_letter('Y')) y = gcode->get_value('Y');
344 if(gcode->has_letter('Z')) z = gcode->get_value('Z');
345 probe_offsets = std::make_tuple(x, y, z);
346 return true;
347
348 } else if(gcode->m == 500 || gcode->m == 503) { // M500 save, M503 display
349 float x, y, z;
350 std::tie(x, y, z) = probe_offsets;
351 gcode->stream->printf(";Probe offsets:\nM565 X%1.5f Y%1.5f Z%1.5f\n", x, y, z);
352 if(save) {
353 if(!isnan(grid[0])) gcode->stream->printf(";Load saved grid\nM375\n");
354 else if(gcode->m == 503) gcode->stream->printf(";WARNING No grid to save\n");
355 }
356 return true;
357 }
358 }
359
360 return false;
361 }
362
363 // These are convenience defines to keep the code as close to the original as possible it also saves memory and flash
364 // set the rectangle in which to probe
365 #define LEFT_PROBE_BED_POSITION (-grid_radius)
366 #define RIGHT_PROBE_BED_POSITION (grid_radius)
367 #define BACK_PROBE_BED_POSITION (grid_radius)
368 #define FRONT_PROBE_BED_POSITION (-grid_radius)
369
370 // probe at the points of a lattice grid
371 #define AUTO_BED_LEVELING_GRID_X ((RIGHT_PROBE_BED_POSITION - LEFT_PROBE_BED_POSITION) / (grid_size - 1))
372 #define AUTO_BED_LEVELING_GRID_Y ((BACK_PROBE_BED_POSITION - FRONT_PROBE_BED_POSITION) / (grid_size - 1))
373
374 #define X_PROBE_OFFSET_FROM_EXTRUDER std::get<0>(probe_offsets)
375 #define Y_PROBE_OFFSET_FROM_EXTRUDER std::get<1>(probe_offsets)
376 #define Z_PROBE_OFFSET_FROM_EXTRUDER std::get<2>(probe_offsets)
377
378 void DeltaGridStrategy::setAdjustFunction(bool on)
379 {
380 if(on) {
381 // set the compensationTransform in robot
382 THEROBOT->compensationTransform = [this](float target[3]) { doCompensation(target); };
383 } else {
384 // clear it
385 THEROBOT->compensationTransform = nullptr;
386 }
387 }
388
389 float DeltaGridStrategy::findBed()
390 {
391 // home
392 zprobe->home();
393
394 // move to an initial position fast so as to not take all day, we move down max_z - initial_height, which is set in config, default 10mm
395 float deltaz = zprobe->getMaxZ() - initial_height;
396 zprobe->coordinated_move(NAN, NAN, -deltaz, zprobe->getFastFeedrate(), true); // relative move
397 zprobe->coordinated_move(0, 0, NAN, zprobe->getFastFeedrate()); // move to 0,0
398
399 // find bed at 0,0 run at slow rate so as to not hit bed hard
400 int s;
401 if(!zprobe->run_probe(s, false)) return NAN;
402
403 // leave the probe zprobe->getProbeHeight() above bed
404 zprobe->return_probe(s);
405
406 float dz= zprobe->getProbeHeight() - zprobe->zsteps_to_mm(s);
407 zprobe->coordinated_move(NAN, NAN, dz, zprobe->getFastFeedrate(), true); // relative move
408
409 return zprobe->zsteps_to_mm(s) + deltaz - zprobe->getProbeHeight(); // distance to move from home to 5mm above bed
410 }
411
412 bool DeltaGridStrategy::doProbe(Gcode *gc)
413 {
414 gc->stream->printf("Delta Grid Probe...\n");
415 setAdjustFunction(false);
416 reset_bed_level();
417
418 if(gc->has_letter('J')) grid_radius = gc->get_value('J'); // override default probe radius, will get saved
419
420 float radius = grid_radius;
421 // find bed, and leave probe probe height above bed
422 float initial_z = findBed();
423 if(isnan(initial_z)) {
424 gc->stream->printf("Finding bed failed, check the maxz and initial height settings\n");
425 return false;
426 }
427
428 gc->stream->printf("Probe start ht is %f mm, probe radius is %f mm, grid size is %dx%d\n", initial_z, radius, grid_size, grid_size);
429
430 // do first probe for 0,0
431 int s;
432 if(!zprobe->doProbeAt(s, -X_PROBE_OFFSET_FROM_EXTRUDER, -Y_PROBE_OFFSET_FROM_EXTRUDER)) return false;
433 float z_reference = zprobe->getProbeHeight() - zprobe->zsteps_to_mm(s); // this should be zero
434 gc->stream->printf("probe at 0,0 is %f mm\n", z_reference);
435
436 // probe all the points in the grid within the given radius
437 for (int yCount = 0; yCount < grid_size; yCount++) {
438 float yProbe = FRONT_PROBE_BED_POSITION + AUTO_BED_LEVELING_GRID_Y * yCount;
439 int xStart, xStop, xInc;
440 if (yCount % 2) {
441 xStart = 0;
442 xStop = grid_size;
443 xInc = 1;
444 } else {
445 xStart = grid_size - 1;
446 xStop = -1;
447 xInc = -1;
448 }
449
450 for (int xCount = xStart; xCount != xStop; xCount += xInc) {
451 float xProbe = LEFT_PROBE_BED_POSITION + AUTO_BED_LEVELING_GRID_X * xCount;
452
453 // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
454 float distance_from_center = sqrtf(xProbe * xProbe + yProbe * yProbe);
455 if (distance_from_center > radius) continue;
456
457 if(!zprobe->doProbeAt(s, xProbe - X_PROBE_OFFSET_FROM_EXTRUDER, yProbe - Y_PROBE_OFFSET_FROM_EXTRUDER)) return false;
458 float measured_z = zprobe->getProbeHeight() - zprobe->zsteps_to_mm(s) - z_reference; // this is the delta z from bed at 0,0
459 gc->stream->printf("DEBUG: X%1.4f, Y%1.4f, Z%1.4f\n", xProbe, yProbe, measured_z);
460 grid[xCount + (grid_size * yCount)] = measured_z;
461 }
462 }
463
464 extrapolate_unprobed_bed_level();
465 print_bed_level(gc->stream);
466
467 setAdjustFunction(true);
468
469 return true;
470 }
471
472 void DeltaGridStrategy::extrapolate_one_point(int x, int y, int xdir, int ydir)
473 {
474 if (!isnan(grid[x + (grid_size*y)])) {
475 return; // Don't overwrite good values.
476 }
477 float a = 2 * grid[(x + xdir) + (y*grid_size)] - grid[(x + xdir * 2) + (y*grid_size)]; // Left to right.
478 float b = 2 * grid[x + ((y + ydir) * grid_size)] - grid[x + ((y + ydir * 2) * grid_size)]; // Front to back.
479 float c = 2 * grid[(x + xdir) + ((y + ydir) * grid_size)] - grid[(x + xdir * 2) + ((y + ydir * 2) * grid_size)]; // Diagonal.
480 float median = c; // Median is robust (ignores outliers).
481 if (a < b) {
482 if (b < c) median = b;
483 if (c < a) median = a;
484 } else { // b <= a
485 if (c < b) median = b;
486 if (a < c) median = a;
487 }
488 grid[x + (grid_size*y)] = median;
489 }
490
491 // Fill in the unprobed points (corners of circular print surface)
492 // using linear extrapolation, away from the center.
493 void DeltaGridStrategy::extrapolate_unprobed_bed_level()
494 {
495 int half = (grid_size - 1) / 2;
496 for (int y = 0; y <= half; y++) {
497 for (int x = 0; x <= half; x++) {
498 if (x + y < 3) continue;
499 extrapolate_one_point(half - x, half - y, x > 1 ? +1 : 0, y > 1 ? +1 : 0);
500 extrapolate_one_point(half + x, half - y, x > 1 ? -1 : 0, y > 1 ? +1 : 0);
501 extrapolate_one_point(half - x, half + y, x > 1 ? +1 : 0, y > 1 ? -1 : 0);
502 extrapolate_one_point(half + x, half + y, x > 1 ? -1 : 0, y > 1 ? -1 : 0);
503 }
504 }
505 }
506
507 void DeltaGridStrategy::doCompensation(float target[3])
508 {
509 // Adjust print surface height by linear interpolation over the bed_level array.
510 int half = (grid_size - 1) / 2;
511 float grid_x = std::max(0.001F - half, std::min(half - 0.001F, target[X_AXIS] / AUTO_BED_LEVELING_GRID_X));
512 float grid_y = std::max(0.001F - half, std::min(half - 0.001F, target[Y_AXIS] / AUTO_BED_LEVELING_GRID_Y));
513 int floor_x = floorf(grid_x);
514 int floor_y = floorf(grid_y);
515 float ratio_x = grid_x - floor_x;
516 float ratio_y = grid_y - floor_y;
517 float z1 = grid[(floor_x + half) + ((floor_y + half) * grid_size)];
518 float z2 = grid[(floor_x + half) + ((floor_y + half + 1) * grid_size)];
519 float z3 = grid[(floor_x + half + 1) + ((floor_y + half) * grid_size)];
520 float z4 = grid[(floor_x + half + 1) + ((floor_y + half + 1) * grid_size)];
521 float left = (1 - ratio_y) * z1 + ratio_y * z2;
522 float right = (1 - ratio_y) * z3 + ratio_y * z4;
523 float offset = (1 - ratio_x) * left + ratio_x * right;
524
525 target[Z_AXIS] += offset;
526
527
528 /*
529 THEKERNEL->streams->printf("//DEBUG: TARGET: %f, %f, %f\n", target[0], target[1], target[2]);
530 THEKERNEL->streams->printf("//DEBUG: grid_x= %f\n", grid_x);
531 THEKERNEL->streams->printf("//DEBUG: grid_y= %f\n", grid_y);
532 THEKERNEL->streams->printf("//DEBUG: floor_x= %d\n", floor_x);
533 THEKERNEL->streams->printf("//DEBUG: floor_y= %d\n", floor_y);
534 THEKERNEL->streams->printf("//DEBUG: ratio_x= %f\n", ratio_x);
535 THEKERNEL->streams->printf("//DEBUG: ratio_y= %f\n", ratio_y);
536 THEKERNEL->streams->printf("//DEBUG: z1= %f\n", z1);
537 THEKERNEL->streams->printf("//DEBUG: z2= %f\n", z2);
538 THEKERNEL->streams->printf("//DEBUG: z3= %f\n", z3);
539 THEKERNEL->streams->printf("//DEBUG: z4= %f\n", z4);
540 THEKERNEL->streams->printf("//DEBUG: left= %f\n", left);
541 THEKERNEL->streams->printf("//DEBUG: right= %f\n", right);
542 THEKERNEL->streams->printf("//DEBUG: offset= %f\n", offset);
543 */
544 }
545
546
547 // Print calibration results for plotting or manual frame adjustment.
548 void DeltaGridStrategy::print_bed_level(StreamOutput *stream)
549 {
550 for (int y = 0; y < grid_size; y++) {
551 for (int x = 0; x < grid_size; x++) {
552 stream->printf("%7.4f ", grid[x + (grid_size*y)]);
553 }
554 stream->printf("\n");
555 }
556 }
557
558 // Reset calibration results to zero.
559 void DeltaGridStrategy::reset_bed_level()
560 {
561 for (int y = 0; y < grid_size; y++) {
562 for (int x = 0; x < grid_size; x++) {
563 grid[x + (grid_size*y)] = NAN;
564 }
565 }
566 }