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")
93 #define x_max_checksum CHECKSUM("x_max")
94 #define y_max_checksum CHECKSUM("y_max")
95 #define do_home_checksum CHECKSUM("do_home")
96 #define is_square_checksum CHECKSUM("is_square")
98 #define GRIDFILE "/sd/delta.grid"
100 DeltaGridStrategy::DeltaGridStrategy(ZProbe
*zprobe
) : LevelingStrategy(zprobe
)
105 DeltaGridStrategy::~DeltaGridStrategy()
107 if(grid
!= nullptr) AHB0
.dealloc(grid
);
110 bool DeltaGridStrategy::handleConfig()
112 grid_radius
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, grid_radius_checksum
)->by_default(50.0F
)->as_number();
113 grid_size
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, grid_size_checksum
)->by_default(7)->as_number();
114 tolerance
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, tolerance_checksum
)->by_default(0.03F
)->as_number();
115 save
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, save_checksum
)->by_default(false)->as_bool();
116 do_home
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, do_home_checksum
)->by_default(true)->as_bool();
117 is_square
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, is_square_checksum
)->by_default(false)->as_bool();
121 x_max
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, x_max_checksum
)->by_default(0.0F
)->as_number();
122 y_max
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, y_max_checksum
)->by_default(0.0F
)->as_number();
124 // intelligently set defaults.
125 if (x_max
>= 1.0F
) grid_radius
= x_max
;
126 if (x_max
< 1.0F
) x_max
= grid_radius
;
127 if (y_max
>= 1.0F
) grid_radius
= y_max
;
128 if (y_max
< 1.0F
) y_max
= grid_radius
;
129 if (x_max
>= 1.0F
&& y_max
>= 1.0F
) grid_radius
= std::max(x_max
, y_max
);
137 // the initial height above the bed we stop the intial move down after home to find the bed
138 // 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)
139 this->initial_height
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, initial_height_checksum
)->by_default(10)->as_number();
141 // Probe offsets xxx,yyy,zzz
143 std::string po
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, probe_offsets_checksum
)->by_default("0,0,0")->as_string();
144 std::vector
<float> v
= parse_number_list(po
.c_str());
146 this->probe_offsets
= std::make_tuple(v
[0], v
[1], v
[2]);
151 grid
= (float *)AHB0
.alloc(grid_size
* grid_size
* sizeof(float));
158 void DeltaGridStrategy::save_grid(StreamOutput
*stream
)
161 stream
->printf("error:No grid to save\n");
165 FILE *fp
= fopen(GRIDFILE
, "w");
167 stream
->printf("error:Failed to open grid file %s\n", GRIDFILE
);
171 if(fwrite(&grid_size
, sizeof(uint8_t), 1, fp
) != 1) {
172 stream
->printf("error:Failed to write grid size\n");
177 if(fwrite(&grid_radius
, sizeof(float), 1, fp
) != 1) {
178 stream
->printf("error:Failed to write grid radius\n");
183 for (int y
= 0; y
< grid_size
; y
++) {
184 for (int x
= 0; x
< grid_size
; x
++) {
185 if(fwrite(&grid
[x
+ (grid_size
*y
)], sizeof(float), 1, fp
) != 1) {
186 stream
->printf("error:Failed to write grid\n");
192 stream
->printf("grid saved to %s\n", GRIDFILE
);
196 bool DeltaGridStrategy::load_grid(StreamOutput
*stream
)
198 FILE *fp
= fopen(GRIDFILE
, "r");
200 stream
->printf("error:Failed to open grid %s\n", GRIDFILE
);
207 if(fread(&size
, sizeof(uint8_t), 1, fp
) != 1) {
208 stream
->printf("error:Failed to read grid size\n");
213 if(size
!= grid_size
) {
214 stream
->printf("error:grid size is different read %d - config %d\n", size
, grid_size
);
219 if(fread(&radius
, sizeof(float), 1, fp
) != 1) {
220 stream
->printf("error:Failed to read grid radius\n");
225 if(radius
!= grid_radius
) {
226 stream
->printf("warning:grid radius is different read %f - config %f, overriding config\n", radius
, grid_radius
);
230 for (int y
= 0; y
< grid_size
; y
++) {
231 for (int x
= 0; x
< grid_size
; x
++) {
232 if(fread(&grid
[x
+ (grid_size
*y
)], sizeof(float), 1, fp
) != 1) {
233 stream
->printf("error:Failed to read grid\n");
239 stream
->printf("grid loaded, radius: %f, size: %d\n", grid_radius
, grid_size
);
244 bool DeltaGridStrategy::probe_grid(int n
, float radius
, StreamOutput
*stream
)
247 stream
->printf("Need at least a 5x5 grid to probe\n");
251 float initial_z
= findBed();
252 if(isnan(initial_z
)) return false;
254 float d
= ((radius
*2) / (n
- 1));
256 for (int c
= 0; c
< n
; ++c
) {
257 float y
= -radius
+ d
*c
;
258 for (int r
= 0; r
< n
; ++r
) {
259 float x
= -radius
+ d
*r
;
260 // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
261 float distance_from_center
= sqrtf(x
*x
+ y
*y
);
263 if ((!is_square
&& (distance_from_center
<= radius
)) ||
264 (is_square
&& (x
< -x_max
|| x
> x_max
|| y
< -y_max
|| y
> y_max
))) {
266 if(!zprobe
->doProbeAt(mm
, x
, y
)) return false;
267 z
= zprobe
->getProbeHeight() - mm
;
269 stream
->printf("%8.4f ", z
);
271 stream
->printf("\n");
276 // taken from Oskars PR #713
277 bool DeltaGridStrategy::probe_spiral(int n
, float radius
, StreamOutput
*stream
)
279 float a
= radius
/ (2 * sqrtf(n
* M_PI
));
280 float step_length
= radius
* radius
/ (2 * a
* n
);
282 float initial_z
= findBed();
283 if(isnan(initial_z
)) return false;
285 auto theta
= [a
](float length
) {return sqrtf(2*length
/a
); };
287 float maxz
= NAN
, minz
= NAN
;
288 for (int i
= 0; i
< n
; i
++) {
289 float angle
= theta(i
* step_length
);
291 // polar to cartesian
292 float x
= r
* cosf(angle
);
293 float y
= r
* sinf(angle
);
296 if (!zprobe
->doProbeAt(mm
, x
, y
)) return false;
297 float z
= zprobe
->getProbeHeight() - mm
;
298 stream
->printf("PROBE: X%1.4f, Y%1.4f, Z%1.4f\n", x
, y
, z
);
299 if(isnan(maxz
) || z
> maxz
) maxz
= z
;
300 if(isnan(minz
) || z
< minz
) minz
= z
;
303 stream
->printf("max: %1.4f, min: %1.4f, delta: %1.4f\n", maxz
, minz
, maxz
-minz
);
307 bool DeltaGridStrategy::handleGcode(Gcode
*gcode
)
310 if (gcode
->g
== 29) { // do a probe to test flatness
311 // first wait for an empty queue i.e. no moves left
312 THEKERNEL
->conveyor
->wait_for_idle();
314 int n
= gcode
->has_letter('I') ? gcode
->get_value('I') : 0;
315 float radius
= grid_radius
;
316 if(gcode
->has_letter('J')) radius
= gcode
->get_value('J'); // override default probe radius
317 if(gcode
->subcode
== 1){
319 probe_spiral(n
, radius
, gcode
->stream
);
322 probe_grid(n
, radius
, gcode
->stream
);
327 } else if( gcode
->g
== 31 ) { // do a grid probe
328 // first wait for an empty queue i.e. no moves left
329 THEKERNEL
->conveyor
->wait_for_idle();
331 if(!doProbe(gcode
)) {
332 gcode
->stream
->printf("Probe failed to complete, check the initial probe height and/or initial_height settings\n");
334 gcode
->stream
->printf("Probe completed\n");
339 } else if(gcode
->has_m
) {
340 if(gcode
->m
== 370 || gcode
->m
== 561) { // M370: Clear bed, M561: Set Identity Transform
341 // delete the compensationTransform in robot
342 setAdjustFunction(false);
344 gcode
->stream
->printf("grid cleared and disabled\n");
347 } else if(gcode
->m
== 374) { // M374: Save grid, M374.1: delete saved grid
348 if(gcode
->subcode
== 1) {
350 gcode
->stream
->printf("%s deleted\n", GRIDFILE
);
352 save_grid(gcode
->stream
);
357 } else if(gcode
->m
== 375) { // M375: load grid, M375.1 display grid
358 if(gcode
->subcode
== 1) {
359 print_bed_level(gcode
->stream
);
361 if(load_grid(gcode
->stream
)) setAdjustFunction(true);
365 } else if(gcode
->m
== 565) { // M565: Set Z probe offsets
366 float x
= 0, y
= 0, z
= 0;
367 if(gcode
->has_letter('X')) x
= gcode
->get_value('X');
368 if(gcode
->has_letter('Y')) y
= gcode
->get_value('Y');
369 if(gcode
->has_letter('Z')) z
= gcode
->get_value('Z');
370 probe_offsets
= std::make_tuple(x
, y
, z
);
373 } else if(gcode
->m
== 500 || gcode
->m
== 503) { // M500 save, M503 display
375 std::tie(x
, y
, z
) = probe_offsets
;
376 gcode
->stream
->printf(";Probe offsets:\nM565 X%1.5f Y%1.5f Z%1.5f\n", x
, y
, z
);
378 if(!isnan(grid
[0])) gcode
->stream
->printf(";Load saved grid\nM375\n");
379 else if(gcode
->m
== 503) gcode
->stream
->printf(";WARNING No grid to save\n");
388 // These are convenience defines to keep the code as close to the original as possible it also saves memory and flash
389 // set the rectangle in which to probe
390 #define LEFT_PROBE_BED_POSITION (-grid_radius)
391 #define RIGHT_PROBE_BED_POSITION (grid_radius)
392 #define BACK_PROBE_BED_POSITION (grid_radius)
393 #define FRONT_PROBE_BED_POSITION (-grid_radius)
395 // probe at the points of a lattice grid
396 #define AUTO_BED_LEVELING_GRID_X ((RIGHT_PROBE_BED_POSITION - LEFT_PROBE_BED_POSITION) / (grid_size - 1))
397 #define AUTO_BED_LEVELING_GRID_Y ((BACK_PROBE_BED_POSITION - FRONT_PROBE_BED_POSITION) / (grid_size - 1))
399 #define X_PROBE_OFFSET_FROM_EXTRUDER std::get<0>(probe_offsets)
400 #define Y_PROBE_OFFSET_FROM_EXTRUDER std::get<1>(probe_offsets)
401 #define Z_PROBE_OFFSET_FROM_EXTRUDER std::get<2>(probe_offsets)
403 void DeltaGridStrategy::setAdjustFunction(bool on
)
406 // set the compensationTransform in robot
407 using std::placeholders::_1
;
408 using std::placeholders::_2
;
409 THEROBOT
->compensationTransform
= std::bind(&DeltaGridStrategy::doCompensation
, this, _1
, _2
); // [this](float *target, bool inverse) { doCompensation(target, inverse); };
412 THEROBOT
->compensationTransform
= nullptr;
416 float DeltaGridStrategy::findBed()
418 if (do_home
) zprobe
->home();
419 // 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
420 float deltaz
= initial_height
;
421 zprobe
->coordinated_move(NAN
, NAN
, deltaz
, zprobe
->getFastFeedrate());
422 zprobe
->coordinated_move(0, 0, NAN
, zprobe
->getFastFeedrate()); // move to 0,0
424 // find bed at 0,0 run at slow rate so as to not hit bed hard
426 if(!zprobe
->run_probe_return(mm
, zprobe
->getSlowFeedrate())) return NAN
;
428 float dz
= zprobe
->getProbeHeight() - mm
;
429 zprobe
->coordinated_move(NAN
, NAN
, dz
, zprobe
->getFastFeedrate(), true); // relative move
431 return mm
+ deltaz
- zprobe
->getProbeHeight(); // distance to move from home to 5mm above bed
434 bool DeltaGridStrategy::doProbe(Gcode
*gc
)
436 gc
->stream
->printf("Delta Grid Probe...\n");
437 setAdjustFunction(false);
440 if(gc
->has_letter('J')) grid_radius
= gc
->get_value('J'); // override default probe radius, will get saved
442 float radius
= grid_radius
;
443 // find bed, and leave probe probe height above bed
444 float initial_z
= findBed();
445 if(isnan(initial_z
)) {
446 gc
->stream
->printf("Finding bed failed, check the maxz and initial height settings\n");
450 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
);
452 // do first probe for 0,0
454 if(!zprobe
->doProbeAt(mm
, -X_PROBE_OFFSET_FROM_EXTRUDER
, -Y_PROBE_OFFSET_FROM_EXTRUDER
)) return false;
455 float z_reference
= zprobe
->getProbeHeight() - mm
; // this should be zero
456 gc
->stream
->printf("probe at 0,0 is %f mm\n", z_reference
);
458 // probe all the points in the grid within the given radius
459 for (int yCount
= 0; yCount
< grid_size
; yCount
++) {
460 float yProbe
= FRONT_PROBE_BED_POSITION
+ AUTO_BED_LEVELING_GRID_Y
* yCount
;
461 int xStart
, xStop
, xInc
;
467 xStart
= grid_size
- 1;
472 for (int xCount
= xStart
; xCount
!= xStop
; xCount
+= xInc
) {
473 float xProbe
= LEFT_PROBE_BED_POSITION
+ AUTO_BED_LEVELING_GRID_X
* xCount
;
475 // avoid probing outside of x min/max on a cartesian
478 if (xProbe
< -x_max
|| xProbe
> x_max
|| yProbe
< -y_max
|| yProbe
> y_max
) continue;
482 // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
483 float distance_from_center
= sqrtf(xProbe
* xProbe
+ yProbe
* yProbe
);
484 if (distance_from_center
> radius
) continue;
487 if(!zprobe
->doProbeAt(mm
, xProbe
- X_PROBE_OFFSET_FROM_EXTRUDER
, yProbe
- Y_PROBE_OFFSET_FROM_EXTRUDER
)) return false;
488 float measured_z
= zprobe
->getProbeHeight() - mm
- z_reference
; // this is the delta z from bed at 0,0
489 gc
->stream
->printf("DEBUG: X%1.4f, Y%1.4f, Z%1.4f\n", xProbe
, yProbe
, measured_z
);
490 grid
[xCount
+ (grid_size
* yCount
)] = measured_z
;
494 extrapolate_unprobed_bed_level();
495 print_bed_level(gc
->stream
);
497 setAdjustFunction(true);
502 void DeltaGridStrategy::extrapolate_one_point(int x
, int y
, int xdir
, int ydir
)
504 if (!isnan(grid
[x
+ (grid_size
*y
)])) {
505 return; // Don't overwrite good values.
507 float a
= 2 * grid
[(x
+ xdir
) + (y
*grid_size
)] - grid
[(x
+ xdir
* 2) + (y
*grid_size
)]; // Left to right.
508 float b
= 2 * grid
[x
+ ((y
+ ydir
) * grid_size
)] - grid
[x
+ ((y
+ ydir
* 2) * grid_size
)]; // Front to back.
509 float c
= 2 * grid
[(x
+ xdir
) + ((y
+ ydir
) * grid_size
)] - grid
[(x
+ xdir
* 2) + ((y
+ ydir
* 2) * grid_size
)]; // Diagonal.
510 float median
= c
; // Median is robust (ignores outliers).
512 if (b
< c
) median
= b
;
513 if (c
< a
) median
= a
;
515 if (c
< b
) median
= b
;
516 if (a
< c
) median
= a
;
518 grid
[x
+ (grid_size
*y
)] = median
;
521 // Fill in the unprobed points (corners of circular print surface)
522 // using linear extrapolation, away from the center.
523 void DeltaGridStrategy::extrapolate_unprobed_bed_level()
525 int half
= (grid_size
- 1) / 2;
526 for (int y
= 0; y
<= half
; y
++) {
527 for (int x
= 0; x
<= half
; x
++) {
528 if (x
+ y
< 3) continue;
529 extrapolate_one_point(half
- x
, half
- y
, x
> 1 ? +1 : 0, y
> 1 ? +1 : 0);
530 extrapolate_one_point(half
+ x
, half
- y
, x
> 1 ? -1 : 0, y
> 1 ? +1 : 0);
531 extrapolate_one_point(half
- x
, half
+ y
, x
> 1 ? +1 : 0, y
> 1 ? -1 : 0);
532 extrapolate_one_point(half
+ x
, half
+ y
, x
> 1 ? -1 : 0, y
> 1 ? -1 : 0);
537 void DeltaGridStrategy::doCompensation(float *target
, bool inverse
)
539 // Adjust print surface height by linear interpolation over the bed_level array.
540 int half
= (grid_size
- 1) / 2;
541 float grid_x
= std::max(0.001F
- half
, std::min(half
- 0.001F
, target
[X_AXIS
] / AUTO_BED_LEVELING_GRID_X
));
542 float grid_y
= std::max(0.001F
- half
, std::min(half
- 0.001F
, target
[Y_AXIS
] / AUTO_BED_LEVELING_GRID_Y
));
543 int floor_x
= floorf(grid_x
);
544 int floor_y
= floorf(grid_y
);
545 float ratio_x
= grid_x
- floor_x
;
546 float ratio_y
= grid_y
- floor_y
;
547 float z1
= grid
[(floor_x
+ half
) + ((floor_y
+ half
) * grid_size
)];
548 float z2
= grid
[(floor_x
+ half
) + ((floor_y
+ half
+ 1) * grid_size
)];
549 float z3
= grid
[(floor_x
+ half
+ 1) + ((floor_y
+ half
) * grid_size
)];
550 float z4
= grid
[(floor_x
+ half
+ 1) + ((floor_y
+ half
+ 1) * grid_size
)];
551 float left
= (1 - ratio_y
) * z1
+ ratio_y
* z2
;
552 float right
= (1 - ratio_y
) * z3
+ ratio_y
* z4
;
553 float offset
= (1 - ratio_x
) * left
+ ratio_x
* right
;
556 target
[Z_AXIS
] -= offset
;
558 target
[Z_AXIS
] += offset
;
562 THEKERNEL->streams->printf("//DEBUG: TARGET: %f, %f, %f\n", target[0], target[1], target[2]);
563 THEKERNEL->streams->printf("//DEBUG: grid_x= %f\n", grid_x);
564 THEKERNEL->streams->printf("//DEBUG: grid_y= %f\n", grid_y);
565 THEKERNEL->streams->printf("//DEBUG: floor_x= %d\n", floor_x);
566 THEKERNEL->streams->printf("//DEBUG: floor_y= %d\n", floor_y);
567 THEKERNEL->streams->printf("//DEBUG: ratio_x= %f\n", ratio_x);
568 THEKERNEL->streams->printf("//DEBUG: ratio_y= %f\n", ratio_y);
569 THEKERNEL->streams->printf("//DEBUG: z1= %f\n", z1);
570 THEKERNEL->streams->printf("//DEBUG: z2= %f\n", z2);
571 THEKERNEL->streams->printf("//DEBUG: z3= %f\n", z3);
572 THEKERNEL->streams->printf("//DEBUG: z4= %f\n", z4);
573 THEKERNEL->streams->printf("//DEBUG: left= %f\n", left);
574 THEKERNEL->streams->printf("//DEBUG: right= %f\n", right);
575 THEKERNEL->streams->printf("//DEBUG: offset= %f\n", offset);
580 // Print calibration results for plotting or manual frame adjustment.
581 void DeltaGridStrategy::print_bed_level(StreamOutput
*stream
)
583 for (int y
= 0; y
< grid_size
; y
++) {
584 for (int x
= 0; x
< grid_size
; x
++) {
585 stream
->printf("%7.4f ", grid
[x
+ (grid_size
*y
)]);
587 stream
->printf("\n");
591 // Reset calibration results to zero.
592 void DeltaGridStrategy::reset_bed_level()
594 for (int y
= 0; y
< grid_size
; y
++) {
595 for (int x
= 0; x
< grid_size
; x
++) {
596 grid
[x
+ (grid_size
*y
)] = NAN
;