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.
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.
12 The strategy must be enabled in the config as well as zprobe.
14 leveling-strategy.delta-grid.enable true
16 The radius of the bed must be specified with...
18 leveling-strategy.delta-grid.radius 50
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
22 The size of the grid can be set with...
24 leveling-strategy.delta-grid.size 7
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
28 Optionally probe offsets from the nozzle or tool head can be defined with...
30 leveling-strategy.delta-grid.probe_offsets 0,0,0 # probe offsetrs x,y,z
32 they may also be set with M565 X0 Y0 Z0
34 If the saved grid is to be loaded on boot then this must be set in the config...
36 leveling-strategy.delta-grid.save true
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
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
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.
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
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
61 M500 saves the probe points
62 M503 displays the current settings
65 #include "DeltaGridStrategy.h"
70 #include "StreamOutputPool.h"
72 #include "checksumm.h"
73 #include "ConfigValue.h"
74 #include "PublicDataRequest.h"
75 #include "PublicData.h"
78 #include "nuts_bolts.h"
80 #include "platform_memory.h"
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")
94 #define GRIDFILE "/sd/delta.grid"
96 DeltaGridStrategy::DeltaGridStrategy(ZProbe
*zprobe
) : LevelingStrategy(zprobe
)
101 DeltaGridStrategy::~DeltaGridStrategy()
103 if(grid
!= nullptr) AHB0
.dealloc(grid
);
106 bool DeltaGridStrategy::handleConfig()
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();
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();
117 // Probe offsets xxx,yyy,zzz
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());
122 this->probe_offsets
= std::make_tuple(v
[0], v
[1], v
[2]);
127 grid
= (float *)AHB0
.alloc(grid_size
* grid_size
* sizeof(float));
134 void DeltaGridStrategy::save_grid(StreamOutput
*stream
)
137 stream
->printf("error:No grid to save\n");
141 FILE *fp
= fopen(GRIDFILE
, "w");
143 stream
->printf("error:Failed to open grid file %s\n", GRIDFILE
);
147 if(fwrite(&grid_size
, sizeof(uint8_t), 1, fp
) != 1) {
148 stream
->printf("error:Failed to write grid size\n");
153 if(fwrite(&grid_radius
, sizeof(float), 1, fp
) != 1) {
154 stream
->printf("error:Failed to write grid radius\n");
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");
168 stream
->printf("grid saved to %s\n", GRIDFILE
);
172 bool DeltaGridStrategy::load_grid(StreamOutput
*stream
)
174 FILE *fp
= fopen(GRIDFILE
, "r");
176 stream
->printf("error:Failed to open grid %s\n", GRIDFILE
);
183 if(fread(&size
, sizeof(uint8_t), 1, fp
) != 1) {
184 stream
->printf("error:Failed to read grid size\n");
189 if(size
!= grid_size
) {
190 stream
->printf("error:grid size is different read %d - config %d\n", size
, grid_size
);
195 if(fread(&radius
, sizeof(float), 1, fp
) != 1) {
196 stream
->printf("error:Failed to read grid radius\n");
201 if(radius
!= grid_radius
) {
202 stream
->printf("warning:grid radius is different read %f - config %f, overriding config\n", radius
, grid_radius
);
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");
215 stream
->printf("grid loaded, radius: %f, size: %d\n", grid_radius
, grid_size
);
220 bool DeltaGridStrategy::probe_grid(int n
, float radius
, StreamOutput
*stream
)
223 stream
->printf("Need at least a 5x5 grid to probe\n");
227 float initial_z
= findBed();
228 if(isnan(initial_z
)) return false;
230 float d
= ((radius
*2) / (n
- 1));
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
);
239 if (distance_from_center
<= radius
) {
241 if(!zprobe
->doProbeAt(s
, x
, y
)) return false;
242 z
= zprobe
->getProbeHeight() - zprobe
->zsteps_to_mm(s
);
244 stream
->printf("%8.4f ", z
);
246 stream
->printf("\n");
251 // taken from Oskars PR #713
252 bool DeltaGridStrategy::probe_spiral(int n
, float radius
, StreamOutput
*stream
)
254 float a
= radius
/ (2 * sqrtf(n
* M_PI
));
255 float step_length
= radius
* radius
/ (2 * a
* n
);
257 float initial_z
= findBed();
258 if(isnan(initial_z
)) return false;
260 auto theta
= [a
](float length
) {return sqrtf(2*length
/a
); };
262 float maxz
= NAN
, minz
= NAN
;
263 for (int i
= 0; i
< n
; i
++) {
264 float angle
= theta(i
* step_length
);
266 // polar to cartesian
267 float x
= r
* cosf(angle
);
268 float y
= r
* sinf(angle
);
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
;
278 stream
->printf("max: %1.4f, min: %1.4f, delta: %1.4f\n", maxz
, minz
, maxz
-minz
);
282 bool DeltaGridStrategy::handleGcode(Gcode
*gcode
)
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();
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){
294 probe_spiral(n
, radius
, gcode
->stream
);
297 probe_grid(n
, radius
, gcode
->stream
);
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();
306 if(!doProbe(gcode
)) {
307 gcode
->stream
->printf("Probe failed to complete, check the initial probe height and/or initial_height settings\n");
309 gcode
->stream
->printf("Probe completed\n");
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);
319 gcode
->stream
->printf("grid cleared and disabled\n");
322 } else if(gcode
->m
== 374) { // M374: Save grid, M374.1: delete saved grid
323 if(gcode
->subcode
== 1) {
325 gcode
->stream
->printf("%s deleted\n", GRIDFILE
);
327 save_grid(gcode
->stream
);
332 } else if(gcode
->m
== 375) { // M375: load grid, M375.1 display grid
333 if(gcode
->subcode
== 1) {
334 print_bed_level(gcode
->stream
);
336 if(load_grid(gcode
->stream
)) setAdjustFunction(true);
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
);
348 } else if(gcode
->m
== 500 || gcode
->m
== 503) { // M500 save, M503 display
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
);
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");
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)
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))
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)
378 void DeltaGridStrategy::setAdjustFunction(bool on
)
381 // set the compensationTransform in robot
382 THEROBOT
->compensationTransform
= [this](float target
[3]) { doCompensation(target
); };
385 THEROBOT
->compensationTransform
= nullptr;
389 float DeltaGridStrategy::findBed()
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
399 // find bed at 0,0 run at slow rate so as to not hit bed hard
401 if(!zprobe
->run_probe(s
, false)) return NAN
;
403 // leave the probe zprobe->getProbeHeight() above bed
404 zprobe
->return_probe(s
);
406 float dz
= zprobe
->getProbeHeight() - zprobe
->zsteps_to_mm(s
);
407 zprobe
->coordinated_move(NAN
, NAN
, dz
, zprobe
->getFastFeedrate(), true); // relative move
409 return zprobe
->zsteps_to_mm(s
) + deltaz
- zprobe
->getProbeHeight(); // distance to move from home to 5mm above bed
412 bool DeltaGridStrategy::doProbe(Gcode
*gc
)
414 gc
->stream
->printf("Delta Grid Probe...\n");
415 setAdjustFunction(false);
418 if(gc
->has_letter('J')) grid_radius
= gc
->get_value('J'); // override default probe radius, will get saved
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");
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
);
430 // do first probe for 0,0
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
);
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
;
445 xStart
= grid_size
- 1;
450 for (int xCount
= xStart
; xCount
!= xStop
; xCount
+= xInc
) {
451 float xProbe
= LEFT_PROBE_BED_POSITION
+ AUTO_BED_LEVELING_GRID_X
* xCount
;
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;
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
;
464 extrapolate_unprobed_bed_level();
465 print_bed_level(gc
->stream
);
467 setAdjustFunction(true);
472 void DeltaGridStrategy::extrapolate_one_point(int x
, int y
, int xdir
, int ydir
)
474 if (!isnan(grid
[x
+ (grid_size
*y
)])) {
475 return; // Don't overwrite good values.
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).
482 if (b
< c
) median
= b
;
483 if (c
< a
) median
= a
;
485 if (c
< b
) median
= b
;
486 if (a
< c
) median
= a
;
488 grid
[x
+ (grid_size
*y
)] = median
;
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()
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);
507 void DeltaGridStrategy::doCompensation(float target
[3])
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
;
525 target
[Z_AXIS
] += offset
;
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);
547 // Print calibration results for plotting or manual frame adjustment.
548 void DeltaGridStrategy::print_bed_level(StreamOutput
*stream
)
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
)]);
554 stream
->printf("\n");
558 // Reset calibration results to zero.
559 void DeltaGridStrategy::reset_bed_level()
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
;