Commit | Line | Data |
---|---|---|
ce9d2bda JM |
1 | #include "DeltaCalibrationStrategy.h" |
2 | #include "Kernel.h" | |
3 | #include "Config.h" | |
4 | #include "Robot.h" | |
5 | #include "StreamOutputPool.h" | |
6 | #include "Gcode.h" | |
7 | #include "checksumm.h" | |
8 | #include "ConfigValue.h" | |
9 | #include "PublicDataRequest.h" | |
10 | #include "EndstopsPublicAccess.h" | |
11 | #include "PublicData.h" | |
12 | #include "Conveyor.h" | |
13 | #include "ZProbe.h" | |
14 | #include "BaseSolution.h" | |
afc5b690 | 15 | #include "StepperMotor.h" |
ce9d2bda JM |
16 | |
17 | #include <tuple> | |
18 | #include <algorithm> | |
19 | ||
119114b4 JM |
20 | #define radius_checksum CHECKSUM("radius") |
21 | #define initial_height_checksum CHECKSUM("initial_height") | |
22 | ||
57e927fa JM |
23 | // deprecated |
24 | #define probe_radius_checksum CHECKSUM("probe_radius") | |
ce9d2bda JM |
25 | |
26 | bool DeltaCalibrationStrategy::handleConfig() | |
27 | { | |
28 | // default is probably wrong | |
57e927fa JM |
29 | float r= THEKERNEL->config->value(leveling_strategy_checksum, delta_calibration_strategy_checksum, radius_checksum)->by_default(-1)->as_number(); |
30 | if(r == -1) { | |
31 | // deprecated config syntax] | |
32 | r = THEKERNEL->config->value(zprobe_checksum, probe_radius_checksum)->by_default(100.0F)->as_number(); | |
33 | } | |
34 | this->probe_radius= r; | |
119114b4 JM |
35 | |
36 | // the initial height above the bed we stop the intial move down after home to find the bed | |
37 | // 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) | |
38 | this->initial_height= THEKERNEL->config->value(leveling_strategy_checksum, delta_calibration_strategy_checksum, initial_height_checksum)->by_default(10)->as_number(); | |
ce9d2bda JM |
39 | return true; |
40 | } | |
41 | ||
42 | bool DeltaCalibrationStrategy::handleGcode(Gcode *gcode) | |
43 | { | |
ce9d2bda JM |
44 | if( gcode->has_g) { |
45 | // G code processing | |
46 | if( gcode->g == 32 ) { // auto calibration for delta, Z bed mapping for cartesian | |
47 | // first wait for an empty queue i.e. no moves left | |
48 | THEKERNEL->conveyor->wait_for_empty_queue(); | |
49 | ||
f6efadb0 | 50 | if(!gcode->has_letter('R')) { |
ce9d2bda JM |
51 | if(!calibrate_delta_endstops(gcode)) { |
52 | gcode->stream->printf("Calibration failed to complete, probe not triggered\n"); | |
53 | return true; | |
54 | } | |
55 | } | |
f6efadb0 | 56 | if(!gcode->has_letter('E')) { |
ce9d2bda JM |
57 | if(!calibrate_delta_radius(gcode)) { |
58 | gcode->stream->printf("Calibration failed to complete, probe not triggered\n"); | |
59 | return true; | |
60 | } | |
61 | } | |
62 | gcode->stream->printf("Calibration complete, save settings with M500\n"); | |
63 | return true; | |
afc5b690 JM |
64 | |
65 | }else if (gcode->g == 29) { | |
66 | // probe the 7 points | |
67 | if(!probe_delta_points(gcode)) { | |
68 | gcode->stream->printf("Calibration failed to complete, probe not triggered\n"); | |
69 | } | |
70 | return true; | |
ce9d2bda JM |
71 | } |
72 | ||
73 | } else if(gcode->has_m) { | |
57e927fa | 74 | // handle mcodes |
ce9d2bda JM |
75 | } |
76 | ||
77 | return false; | |
78 | } | |
79 | ||
ce9d2bda JM |
80 | // calculate the X and Y positions for the three towers given the radius from the center |
81 | static std::tuple<float, float, float, float, float, float> getCoordinates(float radius) | |
82 | { | |
83 | float px = 0.866F * radius; // ~sin(60) | |
84 | float py = 0.5F * radius; // cos(60) | |
85 | float t1x = -px, t1y = -py; // X Tower | |
86 | float t2x = px, t2y = -py; // Y Tower | |
87 | float t3x = 0.0F, t3y = radius; // Z Tower | |
88 | return std::make_tuple(t1x, t1y, t2x, t2y, t3x, t3y); | |
89 | } | |
90 | ||
afc5b690 JM |
91 | |
92 | // Probes the 7 points on a delta can be used for off board calibration | |
93 | bool DeltaCalibrationStrategy::probe_delta_points(Gcode *gcode) | |
94 | { | |
1a207d9c JM |
95 | float bedht= findBed(); |
96 | if(isnan(bedht)) return false; | |
97 | ||
98 | gcode->stream->printf("initial Bed ht is %f mm\n", bedht); | |
99 | ||
100 | // move to start position | |
101 | zprobe->home(); | |
102 | zprobe->coordinated_move(NAN, NAN, -bedht, zprobe->getFastFeedrate(), true); // do a relative move from home to the point above the bed | |
103 | ||
afc5b690 JM |
104 | // get probe points |
105 | float t1x, t1y, t2x, t2y, t3x, t3y; | |
106 | std::tie(t1x, t1y, t2x, t2y, t3x, t3y) = getCoordinates(this->probe_radius); | |
107 | ||
108 | // gather probe points | |
109 | float pp[][2] {{t1x, t1y}, {t2x, t2y}, {t3x, t3y}, {0, 0}, {-t1x, -t1y}, {-t2x, -t2y}, {-t3x, -t3y}}; | |
110 | ||
119114b4 JM |
111 | float max_delta= 0; |
112 | float last_z= NAN; | |
aaf0c0ee JM |
113 | float start_z; |
114 | std::tie(std::ignore, std::ignore, start_z)= THEKERNEL->robot->get_axis_position(); | |
115 | ||
afc5b690 JM |
116 | for(auto& i : pp) { |
117 | int s; | |
118 | if(!zprobe->doProbeAt(s, i[0], i[1])) return false; | |
119 | float z = zprobe->zsteps_to_mm(s); | |
aaf0c0ee JM |
120 | if(gcode->subcode == 0) { |
121 | gcode->stream->printf("X:%1.4f Y:%1.4f Z:%1.4f (%d) A:%1.4f B:%1.4f C:%1.4f\n", | |
122 | i[0], i[1], z, s, | |
123 | THEKERNEL->robot->actuators[0]->get_current_position()+z, | |
124 | THEKERNEL->robot->actuators[1]->get_current_position()+z, | |
125 | THEKERNEL->robot->actuators[2]->get_current_position()+z); | |
126 | ||
127 | }else if(gcode->subcode == 1) { | |
128 | // format that can be pasted here http://escher3d.com/pages/wizards/wizarddelta.php | |
129 | gcode->stream->printf("X%1.4f Y%1.4f Z%1.4f\n", i[0], i[1], start_z - z); | |
130 | } | |
119114b4 JM |
131 | |
132 | if(isnan(last_z)) { | |
133 | last_z= z; | |
134 | }else{ | |
135 | max_delta= std::max(max_delta, fabsf(z-last_z)); | |
136 | } | |
afc5b690 JM |
137 | } |
138 | ||
119114b4 JM |
139 | gcode->stream->printf("max delta: %f\n", max_delta); |
140 | ||
afc5b690 JM |
141 | return true; |
142 | } | |
143 | ||
119114b4 JM |
144 | float DeltaCalibrationStrategy::findBed() |
145 | { | |
146 | // home | |
147 | zprobe->home(); | |
148 | ||
149 | // 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 | |
150 | float deltaz= zprobe->getMaxZ() - initial_height; | |
151 | zprobe->coordinated_move(NAN, NAN, -deltaz, zprobe->getFastFeedrate(), true); | |
152 | ||
153 | // find bed, run at slow rate so as to not hit bed hard | |
154 | int s; | |
155 | if(!zprobe->run_probe(s, false)) return NAN; | |
156 | ||
157 | return zprobe->zsteps_to_mm(s) + deltaz - zprobe->getProbeHeight(); // distance to move from home to 5mm above bed | |
158 | } | |
159 | ||
ce9d2bda JM |
160 | /* Run a calibration routine for a delta |
161 | 1. Home | |
162 | 2. probe for z bed | |
163 | 3. probe initial tower positions | |
164 | 4. set initial trims such that trims will be minimal negative values | |
165 | 5. home, probe three towers again | |
166 | 6. calculate trim offset and apply to all trims | |
167 | 7. repeat 5, 6 until it converges on a solution | |
168 | */ | |
169 | ||
170 | bool DeltaCalibrationStrategy::calibrate_delta_endstops(Gcode *gcode) | |
171 | { | |
f6efadb0 JM |
172 | float target = 0.03F; |
173 | if(gcode->has_letter('I')) target = gcode->get_value('I'); // override default target | |
174 | if(gcode->has_letter('J')) this->probe_radius = gcode->get_value('J'); // override default probe radius | |
ce9d2bda | 175 | |
f6efadb0 JM |
176 | bool keep = false; |
177 | if(gcode->has_letter('K')) keep = true; // keep current settings | |
ce9d2bda JM |
178 | |
179 | gcode->stream->printf("Calibrating Endstops: target %fmm, radius %fmm\n", target, this->probe_radius); | |
180 | ||
181 | // get probe points | |
182 | float t1x, t1y, t2x, t2y, t3x, t3y; | |
183 | std::tie(t1x, t1y, t2x, t2y, t3x, t3y) = getCoordinates(this->probe_radius); | |
184 | ||
f6efadb0 | 185 | float trimx = 0.0F, trimy = 0.0F, trimz = 0.0F; |
ce9d2bda JM |
186 | if(!keep) { |
187 | // zero trim values | |
188 | if(!set_trim(0, 0, 0, gcode->stream)) return false; | |
189 | ||
f6efadb0 | 190 | } else { |
ce9d2bda JM |
191 | // get current trim, and continue from that |
192 | if (get_trim(trimx, trimy, trimz)) { | |
193 | gcode->stream->printf("Current Trim X: %f, Y: %f, Z: %f\r\n", trimx, trimy, trimz); | |
194 | ||
195 | } else { | |
196 | gcode->stream->printf("Could not get current trim, are endstops enabled?\n"); | |
197 | return false; | |
198 | } | |
199 | } | |
200 | ||
119114b4 JM |
201 | // find the bed, as we potentially have a temporary z probe we don't know how low under the nozzle it is |
202 | // so we need to find the initial place that the probe triggers when it hits the bed | |
203 | float bedht= findBed(); | |
204 | if(isnan(bedht)) return false; | |
205 | gcode->stream->printf("initial Bed ht is %f mm\n", bedht); | |
ce9d2bda JM |
206 | |
207 | // move to start position | |
208 | zprobe->home(); | |
209 | zprobe->coordinated_move(NAN, NAN, -bedht, zprobe->getFastFeedrate(), true); // do a relative move from home to the point above the bed | |
210 | ||
211 | // get initial probes | |
212 | // probe the base of the X tower | |
119114b4 | 213 | int s; |
97832d6d | 214 | if(!zprobe->doProbeAt(s, t1x, t1y)) return false; |
57e927fa | 215 | float t1z = zprobe->zsteps_to_mm(s); |
ce9d2bda JM |
216 | gcode->stream->printf("T1-0 Z:%1.4f C:%d\n", t1z, s); |
217 | ||
218 | // probe the base of the Y tower | |
97832d6d | 219 | if(!zprobe->doProbeAt(s, t2x, t2y)) return false; |
57e927fa | 220 | float t2z = zprobe->zsteps_to_mm(s); |
ce9d2bda JM |
221 | gcode->stream->printf("T2-0 Z:%1.4f C:%d\n", t2z, s); |
222 | ||
223 | // probe the base of the Z tower | |
97832d6d | 224 | if(!zprobe->doProbeAt(s, t3x, t3y)) return false; |
57e927fa | 225 | float t3z = zprobe->zsteps_to_mm(s); |
ce9d2bda JM |
226 | gcode->stream->printf("T3-0 Z:%1.4f C:%d\n", t3z, s); |
227 | ||
f6efadb0 | 228 | float trimscale = 1.2522F; // empirically determined |
ce9d2bda | 229 | |
f6efadb0 JM |
230 | auto mm = std::minmax({t1z, t2z, t3z}); |
231 | if((mm.second - mm.first) <= target) { | |
232 | gcode->stream->printf("trim already set within required parameters: delta %f\n", mm.second - mm.first); | |
ce9d2bda JM |
233 | return true; |
234 | } | |
235 | ||
236 | // set trims to worst case so we always have a negative trim | |
f6efadb0 JM |
237 | trimx += (mm.first - t1z) * trimscale; |
238 | trimy += (mm.first - t2z) * trimscale; | |
239 | trimz += (mm.first - t3z) * trimscale; | |
ce9d2bda JM |
240 | |
241 | for (int i = 1; i <= 10; ++i) { | |
242 | // set trim | |
243 | if(!set_trim(trimx, trimy, trimz, gcode->stream)) return false; | |
244 | ||
245 | // home and move probe to start position just above the bed | |
246 | zprobe->home(); | |
247 | zprobe->coordinated_move(NAN, NAN, -bedht, zprobe->getFastFeedrate(), true); // do a relative move from home to the point above the bed | |
248 | ||
249 | // probe the base of the X tower | |
97832d6d | 250 | if(!zprobe->doProbeAt(s, t1x, t1y)) return false; |
57e927fa | 251 | t1z = zprobe->zsteps_to_mm(s); |
ce9d2bda JM |
252 | gcode->stream->printf("T1-%d Z:%1.4f C:%d\n", i, t1z, s); |
253 | ||
254 | // probe the base of the Y tower | |
97832d6d | 255 | if(!zprobe->doProbeAt(s, t2x, t2y)) return false; |
57e927fa | 256 | t2z = zprobe->zsteps_to_mm(s); |
ce9d2bda JM |
257 | gcode->stream->printf("T2-%d Z:%1.4f C:%d\n", i, t2z, s); |
258 | ||
259 | // probe the base of the Z tower | |
97832d6d | 260 | if(!zprobe->doProbeAt(s, t3x, t3y)) return false; |
57e927fa | 261 | t3z = zprobe->zsteps_to_mm(s); |
ce9d2bda JM |
262 | gcode->stream->printf("T3-%d Z:%1.4f C:%d\n", i, t3z, s); |
263 | ||
f6efadb0 JM |
264 | mm = std::minmax({t1z, t2z, t3z}); |
265 | if((mm.second - mm.first) <= target) { | |
266 | gcode->stream->printf("trim set to within required parameters: delta %f\n", mm.second - mm.first); | |
ce9d2bda JM |
267 | break; |
268 | } | |
269 | ||
270 | // set new trim values based on min difference | |
f6efadb0 JM |
271 | trimx += (mm.first - t1z) * trimscale; |
272 | trimy += (mm.first - t2z) * trimscale; | |
273 | trimz += (mm.first - t3z) * trimscale; | |
ce9d2bda JM |
274 | |
275 | // flush the output | |
276 | THEKERNEL->call_event(ON_IDLE); | |
277 | } | |
278 | ||
f6efadb0 JM |
279 | if((mm.second - mm.first) > target) { |
280 | gcode->stream->printf("WARNING: trim did not resolve to within required parameters: delta %f\n", mm.second - mm.first); | |
ce9d2bda JM |
281 | } |
282 | ||
283 | return true; | |
284 | } | |
285 | ||
286 | /* | |
287 | probe edges to get outer positions, then probe center | |
288 | modify the delta radius until center and X converge | |
289 | */ | |
290 | ||
291 | bool DeltaCalibrationStrategy::calibrate_delta_radius(Gcode *gcode) | |
292 | { | |
f6efadb0 JM |
293 | float target = 0.03F; |
294 | if(gcode->has_letter('I')) target = gcode->get_value('I'); // override default target | |
295 | if(gcode->has_letter('J')) this->probe_radius = gcode->get_value('J'); // override default probe radius | |
ce9d2bda JM |
296 | |
297 | gcode->stream->printf("Calibrating delta radius: target %f, radius %f\n", target, this->probe_radius); | |
298 | ||
299 | // get probe points | |
300 | float t1x, t1y, t2x, t2y, t3x, t3y; | |
301 | std::tie(t1x, t1y, t2x, t2y, t3x, t3y) = getCoordinates(this->probe_radius); | |
302 | ||
119114b4 JM |
303 | // find the bed, as we potentially have a temporary z probe we don't know how low under the nozzle it is |
304 | // so we need to find thr initial place that the probe triggers when it hits the bed | |
305 | float bedht= findBed(); | |
306 | if(isnan(bedht)) return false; | |
307 | gcode->stream->printf("initial Bed ht is %f mm\n", bedht); | |
ce9d2bda JM |
308 | |
309 | zprobe->home(); | |
310 | zprobe->coordinated_move(NAN, NAN, -bedht, zprobe->getFastFeedrate(), true); // do a relative move from home to the point above the bed | |
311 | ||
312 | // probe center to get reference point at this Z height | |
313 | int dc; | |
97832d6d | 314 | if(!zprobe->doProbeAt(dc, 0, 0)) return false; |
57e927fa JM |
315 | gcode->stream->printf("CT Z:%1.3f C:%d\n", zprobe->zsteps_to_mm(dc), dc); |
316 | float cmm = zprobe->zsteps_to_mm(dc); | |
ce9d2bda JM |
317 | |
318 | // get current delta radius | |
f6efadb0 | 319 | float delta_radius = 0.0F; |
ce9d2bda JM |
320 | BaseSolution::arm_options_t options; |
321 | if(THEKERNEL->robot->arm_solution->get_optional(options)) { | |
f6efadb0 | 322 | delta_radius = options['R']; |
ce9d2bda JM |
323 | } |
324 | if(delta_radius == 0.0F) { | |
325 | gcode->stream->printf("This appears to not be a delta arm solution\n"); | |
326 | return false; | |
327 | } | |
328 | options.clear(); | |
329 | ||
489cf67d | 330 | bool good= false; |
f6efadb0 | 331 | float drinc = 2.5F; // approx |
ce9d2bda JM |
332 | for (int i = 1; i <= 10; ++i) { |
333 | // probe t1, t2, t3 and get average, but use coordinated moves, probing center won't change | |
334 | int dx, dy, dz; | |
97832d6d | 335 | if(!zprobe->doProbeAt(dx, t1x, t1y)) return false; |
57e927fa | 336 | gcode->stream->printf("T1-%d Z:%1.3f C:%d\n", i, zprobe->zsteps_to_mm(dx), dx); |
97832d6d | 337 | if(!zprobe->doProbeAt(dy, t2x, t2y)) return false; |
57e927fa | 338 | gcode->stream->printf("T2-%d Z:%1.3f C:%d\n", i, zprobe->zsteps_to_mm(dy), dy); |
97832d6d | 339 | if(!zprobe->doProbeAt(dz, t3x, t3y)) return false; |
57e927fa | 340 | gcode->stream->printf("T3-%d Z:%1.3f C:%d\n", i, zprobe->zsteps_to_mm(dz), dz); |
ce9d2bda JM |
341 | |
342 | // now look at the difference and reduce it by adjusting delta radius | |
57e927fa | 343 | float m = zprobe->zsteps_to_mm((dx + dy + dz) / 3.0F); |
f6efadb0 | 344 | float d = cmm - m; |
ce9d2bda JM |
345 | gcode->stream->printf("C-%d Z-ave:%1.4f delta: %1.3f\n", i, m, d); |
346 | ||
489cf67d JM |
347 | if(abs(d) <= target){ |
348 | good= true; | |
349 | break; // resolution of success | |
350 | } | |
ce9d2bda JM |
351 | |
352 | // increase delta radius to adjust for low center | |
353 | // decrease delta radius to adjust for high center | |
f6efadb0 | 354 | delta_radius += (d * drinc); |
ce9d2bda JM |
355 | |
356 | // set the new delta radius | |
f6efadb0 | 357 | options['R'] = delta_radius; |
ce9d2bda JM |
358 | THEKERNEL->robot->arm_solution->set_optional(options); |
359 | gcode->stream->printf("Setting delta radius to: %1.4f\n", delta_radius); | |
360 | ||
361 | zprobe->home(); | |
362 | zprobe->coordinated_move(NAN, NAN, -bedht, zprobe->getFastFeedrate(), true); // needs to be a relative coordinated move | |
363 | ||
364 | // flush the output | |
365 | THEKERNEL->call_event(ON_IDLE); | |
366 | } | |
489cf67d JM |
367 | |
368 | if(!good) { | |
369 | gcode->stream->printf("WARNING: delta radius did not resolve to within required parameters: %f\n", target); | |
370 | } | |
371 | ||
ce9d2bda JM |
372 | return true; |
373 | } | |
374 | ||
375 | bool DeltaCalibrationStrategy::set_trim(float x, float y, float z, StreamOutput *stream) | |
376 | { | |
f6efadb0 JM |
377 | float t[3] {x, y, z}; |
378 | bool ok = PublicData::set_value( endstops_checksum, trim_checksum, t); | |
ce9d2bda JM |
379 | |
380 | if (ok) { | |
381 | stream->printf("set trim to X:%f Y:%f Z:%f\n", x, y, z); | |
382 | } else { | |
383 | stream->printf("unable to set trim, is endstops enabled?\n"); | |
384 | } | |
385 | ||
386 | return ok; | |
387 | } | |
388 | ||
f6efadb0 | 389 | bool DeltaCalibrationStrategy::get_trim(float &x, float &y, float &z) |
ce9d2bda JM |
390 | { |
391 | void *returned_data; | |
392 | bool ok = PublicData::get_value( endstops_checksum, trim_checksum, &returned_data ); | |
393 | ||
394 | if (ok) { | |
395 | float *trim = static_cast<float *>(returned_data); | |
f6efadb0 JM |
396 | x = trim[0]; |
397 | y = trim[1]; | |
398 | z = trim[2]; | |
ce9d2bda JM |
399 | return true; |
400 | } | |
401 | return false; | |
402 | } |