1 // This code is derived from (and mostly copied from) Johann Rocholls code at https://github.com/jcrocholl/Marlin/blob/deltabot/Marlin/Marlin_main.cpp
2 // license is the same as his code.
4 #include "DeltaGridStrategy.h"
9 #include "StreamOutputPool.h"
11 #include "checksumm.h"
12 #include "ConfigValue.h"
13 #include "PublicDataRequest.h"
14 #include "PublicData.h"
17 #include "nuts_bolts.h"
25 #define grid_radius_checksum CHECKSUM("radius")
26 #define grid_resolution_checksum CHECKSUM("resolution")
27 #define tolerance_checksum CHECKSUM("tolerance")
28 #define save_checksum CHECKSUM("save")
29 #define probe_offsets_checksum CHECKSUM("probe_offsets")
30 #define initial_height_checksum CHECKSUM("initial_height")
32 #define GRIDFILE "/sd/delta.grid"
34 DeltaGridStrategy::DeltaGridStrategy(ZProbe
*zprobe
) : LevelingStrategy(zprobe
)
36 // TODO allocate grid in AHB0 or AHB1
40 DeltaGridStrategy::~DeltaGridStrategy()
45 bool DeltaGridStrategy::handleConfig()
47 grid_radius
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, grid_radius_checksum
)->by_default(50.0F
)->as_number();
48 //grid_resolution = THEKERNEL->config->value(leveling_strategy_checksum, delta_grid_leveling_strategy_checksum, grid_radius_checksum)->by_default(7)->as_number();
49 tolerance
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, tolerance_checksum
)->by_default(0.03F
)->as_number();
50 save
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, save_checksum
)->by_default(false)->as_bool();
51 // the initial height above the bed we stop the intial move down after home to find the bed
52 // 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)
53 this->initial_height
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, initial_height_checksum
)->by_default(10)->as_number();
55 // Probe offsets xxx,yyy,zzz
57 std::string po
= THEKERNEL
->config
->value(leveling_strategy_checksum
, delta_grid_leveling_strategy_checksum
, probe_offsets_checksum
)->by_default("0,0,0")->as_string();
58 std::vector
<float> v
= parse_number_list(po
.c_str());
60 this->probe_offsets
= std::make_tuple(v
[0], v
[1], v
[2]);
65 // load the saved grid file
66 if(save
) load_grid(nullptr);
71 void DeltaGridStrategy::save_grid(StreamOutput
*stream
)
73 FILE *fp
= fopen(GRIDFILE
, "w");
75 stream
->printf("error:Failed to open grid\n");
79 for (int y
= 0; y
< AUTO_BED_LEVELING_GRID_POINTS
; y
++) {
80 for (int x
= 0; x
< AUTO_BED_LEVELING_GRID_POINTS
; x
++) {
81 if(fwrite(&grid
[x
][y
], sizeof(float), 1, fp
) != 1) {
82 stream
->printf("error:Failed to write grid\n");
88 stream
->printf("grid saved to %s\n", GRIDFILE
);
92 bool DeltaGridStrategy::load_grid(StreamOutput
*stream
)
94 FILE *fp
= fopen(GRIDFILE
, "r");
96 if(stream
!= nullptr) stream
->printf("error:Failed to open grid\n");
100 for (int y
= 0; y
< AUTO_BED_LEVELING_GRID_POINTS
; y
++) {
101 for (int x
= 0; x
< AUTO_BED_LEVELING_GRID_POINTS
; x
++) {
102 if(fread(&grid
[x
][y
], sizeof(float), 1, fp
) != 1) {
103 if(stream
!= nullptr) stream
->printf("error:Failed to read grid\n");
109 stream
->printf("grid loaded from %s\n", GRIDFILE
);
114 bool DeltaGridStrategy::handleGcode(Gcode
*gcode
)
117 if( gcode
->g
== 31 ) { // do probe (should be 32 but use 31 as deltacalibration will usually also be enabled)
118 // first wait for an empty queue i.e. no moves left
119 THEKERNEL
->conveyor
->wait_for_empty_queue();
121 if(!doProbe(gcode
)) {
122 gcode
->stream
->printf("Probe failed to complete, probe not triggered or other error\n");
124 gcode
->stream
->printf("Probe completed\n");
129 } else if(gcode
->has_m
) {
130 if(gcode
->m
== 370 || gcode
->m
== 561) { // M370: Clear bed, M561: Set Identity Transform
131 // delete the compensationTransform in robot
132 setAdjustFunction(false);
136 } else if(gcode
->m
== 374) { // M374: Save grid, M374.1: delete saved grid
137 if(gcode
->subcode
== 1) {
139 gcode
->stream
->printf("%s deleted\n", GRIDFILE
);
141 save_grid(gcode
->stream
);
146 } else if(gcode
->m
== 375) { // M375: load grid, M375.1 display grid
147 if(gcode
->subcode
== 1) {
148 print_bed_level(gcode
->stream
);
150 if(load_grid(gcode
->stream
)) setAdjustFunction(true);
153 } else if(gcode
->m
== 565) { // M565: Set Z probe offsets
154 float x
= 0, y
= 0, z
= 0;
155 if(gcode
->has_letter('X')) x
= gcode
->get_value('X');
156 if(gcode
->has_letter('Y')) y
= gcode
->get_value('Y');
157 if(gcode
->has_letter('Z')) z
= gcode
->get_value('Z');
158 probe_offsets
= std::make_tuple(x
, y
, z
);
161 } else if(gcode
->m
== 500 || gcode
->m
== 503) { // M500 save, M503 display
163 std::tie(x
, y
, z
) = probe_offsets
;
164 gcode
->stream
->printf(";Probe offsets:\nM565 X%1.5f Y%1.5f Z%1.5f\n", x
, y
, z
);
165 if(save
&& gcode
->m
== 500) gcode
->stream
->printf(";Load saved grid\nM375\n");
173 // These are convenience defines to keep the code as close to the original as possible
174 // set the rectangle in which to probe
175 #define DELTA_PROBABLE_RADIUS (grid_radius)
176 #define LEFT_PROBE_BED_POSITION (-DELTA_PROBABLE_RADIUS)
177 #define RIGHT_PROBE_BED_POSITION (DELTA_PROBABLE_RADIUS)
178 #define BACK_PROBE_BED_POSITION (DELTA_PROBABLE_RADIUS)
179 #define FRONT_PROBE_BED_POSITION (-DELTA_PROBABLE_RADIUS)
181 // probe at the points of a lattice grid
182 //#define AUTO_BED_LEVELING_GRID_POINTS (grid_resolution)
183 #define AUTO_BED_LEVELING_GRID_X ((RIGHT_PROBE_BED_POSITION - LEFT_PROBE_BED_POSITION) / (AUTO_BED_LEVELING_GRID_POINTS - 1))
184 #define AUTO_BED_LEVELING_GRID_Y ((BACK_PROBE_BED_POSITION - FRONT_PROBE_BED_POSITION) / (AUTO_BED_LEVELING_GRID_POINTS - 1))
186 #define X_PROBE_OFFSET_FROM_EXTRUDER std::get<0>(probe_offsets)
187 #define Y_PROBE_OFFSET_FROM_EXTRUDER std::get<1>(probe_offsets)
188 #define Z_PROBE_OFFSET_FROM_EXTRUDER std::get<2>(probe_offsets)
190 void DeltaGridStrategy::setAdjustFunction(bool on
)
193 // set the compensationTransform in robot
194 THEKERNEL
->robot
->compensationTransform
= [this](float target
[3]) { doCompensation(target
); };
197 THEKERNEL
->robot
->compensationTransform
= nullptr;
201 float DeltaGridStrategy::findBed()
206 // 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
207 float deltaz
= zprobe
->getMaxZ() - initial_height
;
208 zprobe
->coordinated_move(NAN
, NAN
, -deltaz
, zprobe
->getFastFeedrate(), true); // relative move
209 zprobe
->coordinated_move(0, 0, NAN
, zprobe
->getFastFeedrate()); // move to 0,0
211 // find bed at 0,0 run at slow rate so as to not hit bed hard
213 if(!zprobe
->run_probe(s
, false)) return NAN
;
215 return zprobe
->zsteps_to_mm(s
) + deltaz
- zprobe
->getProbeHeight(); // distance to move from home to 5mm above bed
218 bool DeltaGridStrategy::doProbe(Gcode
*gc
)
221 setAdjustFunction(false);
223 float initial_z
= findBed();
224 if(isnan(initial_z
)) return false;
225 gc
->stream
->printf("initial Bed ht is %f mm\n", initial_z
);
227 // we start the probe zprobe->getProbeHeight() above the bed
229 zprobe
->coordinated_move(NAN
, NAN
, -initial_z
, zprobe
->getFastFeedrate(), true); // do a relative move from home to the point above the bed
231 // do first probe for 0,0
233 if(!zprobe
->doProbeAt(s
, -X_PROBE_OFFSET_FROM_EXTRUDER
, -Y_PROBE_OFFSET_FROM_EXTRUDER
)) return false;
234 float z_reference
= zprobe
->getProbeHeight() - zprobe
->zsteps_to_mm(s
); // this should be zero
235 gc
->stream
->printf("probe at 0,0 is %f mm\n", z_reference
);
237 float radius
= DELTA_PROBABLE_RADIUS
;
238 if(gc
->has_letter('J')) radius
= gc
->get_value('J'); // override default probe radius
240 for (int yCount
= 0; yCount
< AUTO_BED_LEVELING_GRID_POINTS
; yCount
++) {
241 float yProbe
= FRONT_PROBE_BED_POSITION
+ AUTO_BED_LEVELING_GRID_Y
* yCount
;
242 int xStart
, xStop
, xInc
;
245 xStop
= AUTO_BED_LEVELING_GRID_POINTS
;
248 xStart
= AUTO_BED_LEVELING_GRID_POINTS
- 1;
253 for (int xCount
= xStart
; xCount
!= xStop
; xCount
+= xInc
) {
254 float xProbe
= LEFT_PROBE_BED_POSITION
+ AUTO_BED_LEVELING_GRID_X
* xCount
;
256 // Avoid probing the corners (outside the round or hexagon print surface) on a delta printer.
257 float distance_from_center
= sqrtf(xProbe
* xProbe
+ yProbe
* yProbe
);
258 if (distance_from_center
> radius
) continue;
260 if(!zprobe
->doProbeAt(s
, xProbe
-X_PROBE_OFFSET_FROM_EXTRUDER
, yProbe
-Y_PROBE_OFFSET_FROM_EXTRUDER
)) return false;
261 float measured_z
= zprobe
->getProbeHeight() - zprobe
->zsteps_to_mm(s
) - z_reference
; // this is the delta z from bed at 0,0
262 gc
->stream
->printf("DEBUG: X%1.4f, Y%1.4f, Z%1.4f\n", xProbe
-X_PROBE_OFFSET_FROM_EXTRUDER
, yProbe
-Y_PROBE_OFFSET_FROM_EXTRUDER
, measured_z
);
263 grid
[xCount
][yCount
] = measured_z
;
267 extrapolate_unprobed_bed_level();
268 print_bed_level(gc
->stream
);
270 setAdjustFunction(true);
275 void DeltaGridStrategy::extrapolate_one_point(int x
, int y
, int xdir
, int ydir
)
277 if (!isnan(grid
[x
][y
])) {
278 return; // Don't overwrite good values.
280 float a
= 2 * grid
[x
+ xdir
][y
] - grid
[x
+ xdir
* 2][y
]; // Left to right.
281 float b
= 2 * grid
[x
][y
+ ydir
] - grid
[x
][y
+ ydir
* 2]; // Front to back.
282 float c
= 2 * grid
[x
+ xdir
][y
+ ydir
] - grid
[x
+ xdir
* 2][y
+ ydir
* 2]; // Diagonal.
283 float median
= c
; // Median is robust (ignores outliers).
285 if (b
< c
) median
= b
;
286 if (c
< a
) median
= a
;
288 if (c
< b
) median
= b
;
289 if (a
< c
) median
= a
;
294 // Fill in the unprobed points (corners of circular print surface)
295 // using linear extrapolation, away from the center.
296 void DeltaGridStrategy::extrapolate_unprobed_bed_level()
298 int half
= (AUTO_BED_LEVELING_GRID_POINTS
- 1) / 2;
299 for (int y
= 0; y
<= half
; y
++) {
300 for (int x
= 0; x
<= half
; x
++) {
301 if (x
+ y
< 3) continue;
302 extrapolate_one_point(half
- x
, half
- y
, x
> 1 ? +1 : 0, y
> 1 ? +1 : 0);
303 extrapolate_one_point(half
+ x
, half
- y
, x
> 1 ? -1 : 0, y
> 1 ? +1 : 0);
304 extrapolate_one_point(half
- x
, half
+ y
, x
> 1 ? +1 : 0, y
> 1 ? -1 : 0);
305 extrapolate_one_point(half
+ x
, half
+ y
, x
> 1 ? -1 : 0, y
> 1 ? -1 : 0);
310 void DeltaGridStrategy::doCompensation(float target
[3])
312 // Adjust print surface height by linear interpolation over the bed_level array.
313 int half
= (AUTO_BED_LEVELING_GRID_POINTS
- 1) / 2;
314 float grid_x
= std::max(0.001F
- half
, std::min(half
- 0.001F
, target
[X_AXIS
] / AUTO_BED_LEVELING_GRID_X
));
315 float grid_y
= std::max(0.001F
- half
, std::min(half
- 0.001F
, target
[Y_AXIS
] / AUTO_BED_LEVELING_GRID_Y
));
316 int floor_x
= floorf(grid_x
);
317 int floor_y
= floorf(grid_y
);
318 float ratio_x
= grid_x
- floor_x
;
319 float ratio_y
= grid_y
- floor_y
;
320 float z1
= grid
[floor_x
+ half
][floor_y
+ half
];
321 float z2
= grid
[floor_x
+ half
][floor_y
+ half
+ 1];
322 float z3
= grid
[floor_x
+ half
+ 1][floor_y
+ half
];
323 float z4
= grid
[floor_x
+ half
+ 1][floor_y
+ half
+ 1];
324 float left
= (1 - ratio_y
) * z1
+ ratio_y
* z2
;
325 float right
= (1 - ratio_y
) * z3
+ ratio_y
* z4
;
326 float offset
= (1 - ratio_x
) * left
+ ratio_x
* right
;
328 target
[Z_AXIS
] += offset
;
332 THEKERNEL->streams->printf("//DEBUG: TARGET: %f, %f, %f\n", target[0], target[1], target[2]);
333 THEKERNEL->streams->printf("//DEBUG: grid_x= %f\n", grid_x);
334 THEKERNEL->streams->printf("//DEBUG: grid_y= %f\n", grid_y);
335 THEKERNEL->streams->printf("//DEBUG: floor_x= %d\n", floor_x);
336 THEKERNEL->streams->printf("//DEBUG: floor_y= %d\n", floor_y);
337 THEKERNEL->streams->printf("//DEBUG: ratio_x= %f\n", ratio_x);
338 THEKERNEL->streams->printf("//DEBUG: ratio_y= %f\n", ratio_y);
339 THEKERNEL->streams->printf("//DEBUG: z1= %f\n", z1);
340 THEKERNEL->streams->printf("//DEBUG: z2= %f\n", z2);
341 THEKERNEL->streams->printf("//DEBUG: z3= %f\n", z3);
342 THEKERNEL->streams->printf("//DEBUG: z4= %f\n", z4);
343 THEKERNEL->streams->printf("//DEBUG: left= %f\n", left);
344 THEKERNEL->streams->printf("//DEBUG: right= %f\n", right);
345 THEKERNEL->streams->printf("//DEBUG: offset= %f\n", offset);
350 // Print calibration results for plotting or manual frame adjustment.
351 void DeltaGridStrategy::print_bed_level(StreamOutput
*stream
)
353 for (int y
= 0; y
< AUTO_BED_LEVELING_GRID_POINTS
; y
++) {
354 for (int x
= 0; x
< AUTO_BED_LEVELING_GRID_POINTS
; x
++) {
355 stream
->printf("%1.4f ", grid
[x
][y
]);
357 stream
->printf("\n");
361 // Reset calibration results to zero.
362 void DeltaGridStrategy::reset_bed_level()
364 for (int y
= 0; y
< AUTO_BED_LEVELING_GRID_POINTS
; y
++) {
365 for (int x
= 0; x
< AUTO_BED_LEVELING_GRID_POINTS
; x
++) {