c8f848a5a8b643a8d88b33905fbfedcb9e9a11c2
[clinton/Smoothieware.git] / src / modules / tools / zprobe / ZProbe.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "ZProbe.h"
9
10 #include "Kernel.h"
11 #include "BaseSolution.h"
12 #include "Config.h"
13 #include "Robot.h"
14 #include "StepperMotor.h"
15 #include "StreamOutputPool.h"
16 #include "Gcode.h"
17 #include "Conveyor.h"
18 #include "Stepper.h"
19 #include "checksumm.h"
20 #include "ConfigValue.h"
21 #include "SlowTicker.h"
22 #include "Planner.h"
23 #include "SerialMessage.h"
24 #include "PublicDataRequest.h"
25 #include "EndstopsPublicAccess.h"
26 #include "PublicData.h"
27
28 #include <tuple>
29 #include <algorithm>
30
31 #define zprobe_checksum CHECKSUM("zprobe")
32 #define enable_checksum CHECKSUM("enable")
33 #define probe_pin_checksum CHECKSUM("probe_pin")
34 #define debounce_count_checksum CHECKSUM("debounce_count")
35 #define slow_feedrate_checksum CHECKSUM("slow_feedrate")
36 #define fast_feedrate_checksum CHECKSUM("fast_feedrate")
37 #define probe_radius_checksum CHECKSUM("probe_radius")
38 #define probe_height_checksum CHECKSUM("probe_height")
39
40 // from endstop section
41 #define delta_homing_checksum CHECKSUM("delta_homing")
42
43 #define X_AXIS 0
44 #define Y_AXIS 1
45 #define Z_AXIS 2
46
47 #define STEPPER THEKERNEL->robot->actuators
48 #define STEPS_PER_MM(a) (STEPPER[a]->get_steps_per_mm())
49 #define Z_STEPS_PER_MM STEPS_PER_MM(Z_AXIS)
50
51 #define abs(a) ((a<0) ? -a : a)
52
53 void ZProbe::on_module_loaded()
54 {
55 // if the module is disabled -> do nothing
56 if(!THEKERNEL->config->value( zprobe_checksum, enable_checksum )->by_default(false)->as_bool()) {
57 // as this module is not needed free up the resource
58 delete this;
59 return;
60 }
61 this->running = false;
62
63 // load settings
64 this->on_config_reload(this);
65 // register event-handlers
66 register_for_event(ON_CONFIG_RELOAD);
67 register_for_event(ON_GCODE_RECEIVED);
68 register_for_event(ON_IDLE);
69
70 THEKERNEL->slow_ticker->attach( THEKERNEL->stepper->get_acceleration_ticks_per_second() , this, &ZProbe::acceleration_tick );
71 }
72
73 void ZProbe::on_config_reload(void *argument)
74 {
75 this->pin.from_string( THEKERNEL->config->value(zprobe_checksum, probe_pin_checksum)->by_default("nc" )->as_string())->as_input();
76 this->debounce_count = THEKERNEL->config->value(zprobe_checksum, debounce_count_checksum)->by_default(0 )->as_number();
77
78 // see what type of arm solution we need to use
79 this->is_delta = THEKERNEL->config->value(delta_homing_checksum)->by_default(false)->as_bool();
80 if(this->is_delta) {
81 // default is probably wrong
82 this->probe_radius = THEKERNEL->config->value(zprobe_checksum, probe_radius_checksum)->by_default(100.0F)->as_number();
83 }
84
85 this->probe_height = THEKERNEL->config->value(zprobe_checksum, probe_height_checksum)->by_default(5.0F)->as_number();
86 this->slow_feedrate = THEKERNEL->config->value(zprobe_checksum, slow_feedrate_checksum)->by_default(5)->as_number(); // feedrate in mm/sec
87 this->fast_feedrate = THEKERNEL->config->value(zprobe_checksum, fast_feedrate_checksum)->by_default(100)->as_number(); // feedrate in mm/sec
88 }
89
90 bool ZProbe::wait_for_probe(int steps[3])
91 {
92 unsigned int debounce = 0;
93 while(true) {
94 THEKERNEL->call_event(ON_IDLE);
95 // if no stepper is moving, moves are finished and there was no touch
96 if( !STEPPER[X_AXIS]->is_moving() && !STEPPER[Y_AXIS]->is_moving() && !STEPPER[Z_AXIS]->is_moving() ) {
97 return false;
98 }
99
100 // if the touchprobe is active...
101 if( this->pin.get() ) {
102 //...increase debounce counter...
103 if( debounce < debounce_count) {
104 // ...but only if the counter hasn't reached the max. value
105 debounce++;
106 } else {
107 // ...otherwise stop the steppers, return its remaining steps
108 for( int i = X_AXIS; i <= Z_AXIS; i++ ) {
109 steps[i] = 0;
110 if ( STEPPER[i]->is_moving() ) {
111 steps[i] = STEPPER[i]->get_stepped();
112 STEPPER[i]->move(0, 0);
113 }
114 }
115 return true;
116 }
117 } else {
118 // The probe was not hit yet, reset debounce counter
119 debounce = 0;
120 }
121 }
122 }
123
124 void ZProbe::on_idle(void *argument)
125 {
126 }
127
128 // single probe and report amount moved
129 bool ZProbe::run_probe(int& steps, bool fast)
130 {
131 // Enable the motors
132 THEKERNEL->stepper->turn_enable_pins_on();
133 this->current_feedrate = (fast ? this->fast_feedrate : this->slow_feedrate) * Z_STEPS_PER_MM; // steps/sec
134
135 // move Z down
136 STEPPER[Z_AXIS]->set_speed(0); // will be increased by acceleration tick
137 STEPPER[Z_AXIS]->move(true, 1000 * Z_STEPS_PER_MM); // always probes down, no more than 1000mm TODO should be 2*maxz
138 if(this->is_delta) {
139 // for delta need to move all three actuators
140 STEPPER[X_AXIS]->set_speed(0);
141 STEPPER[X_AXIS]->move(true, 1000 * STEPS_PER_MM(X_AXIS));
142 STEPPER[Y_AXIS]->set_speed(0);
143 STEPPER[Y_AXIS]->move(true, 1000 * STEPS_PER_MM(Y_AXIS));
144 }
145
146 this->running = true;
147
148 int s[3];
149 bool r = wait_for_probe(s);
150 steps= s[Z_AXIS]; // only need z
151 this->running = false;
152 return r;
153 }
154
155 bool ZProbe::return_probe(int steps)
156 {
157 // move probe back to where it was
158 this->current_feedrate = this->fast_feedrate * Z_STEPS_PER_MM; // feedrate in steps/sec
159 bool dir= steps < 0;
160 steps= abs(steps);
161
162 STEPPER[Z_AXIS]->set_speed(0); // will be increased by acceleration tick
163 STEPPER[Z_AXIS]->move(dir, steps);
164 if(this->is_delta) {
165 STEPPER[X_AXIS]->set_speed(0);
166 STEPPER[X_AXIS]->move(dir, steps);
167 STEPPER[Y_AXIS]->set_speed(0);
168 STEPPER[Y_AXIS]->move(dir, steps);
169 }
170
171 this->running = true;
172 while(STEPPER[X_AXIS]->is_moving() || STEPPER[Y_AXIS]->is_moving() || STEPPER[Z_AXIS]->is_moving()) {
173 // wait for it to complete
174 THEKERNEL->call_event(ON_IDLE);
175 }
176
177 this->running = false;
178
179 return true;
180 }
181
182 // calculate the X and Y positions for the three towers given the radius from the center
183 static std::tuple<float, float, float, float, float, float> getCoordinates(float radius)
184 {
185 float px = 0.866F * radius; // ~sin(60)
186 float py = 0.5F * radius; // cos(60)
187 float t1x = -px, t1y = -py; // X Tower
188 float t2x = px, t2y = -py; // Y Tower
189 float t3x = 0.0F, t3y = radius; // Z Tower
190 return std::make_tuple(t1x, t1y, t2x, t2y, t3x, t3y);
191 }
192
193 bool ZProbe::probe_delta_tower(int& steps, float x, float y)
194 {
195 int s;
196 // move to tower
197 coordinated_move(x, y, NAN, this->fast_feedrate);
198 if(!run_probe(s)) return false;
199
200 // return to original Z
201 return_probe(s);
202 steps= s;
203
204 return true;
205 }
206
207 /* Run a calibration routine for a delta
208 1. Home
209 2. probe for z bed
210 3. probe initial tower positions
211 4. set initial trims such that trims will be minimal negative values
212 5. home, probe three towers again
213 6. calculate trim offset and apply to all trims
214 7. repeat 5, 6 until it converges on a solution
215 */
216
217 bool ZProbe::calibrate_delta_endstops(Gcode *gcode)
218 {
219 float target= 0.03F;
220 if(gcode->has_letter('I')) target= gcode->get_value('I'); // override default target
221 if(gcode->has_letter('J')) this->probe_radius= gcode->get_value('J'); // override default probe radius
222
223 bool keep= false;
224 if(gcode->has_letter('K')) keep= true; // keep current settings
225
226 gcode->stream->printf("Calibrating Endstops: target %fmm, radius %fmm\n", target, this->probe_radius);
227
228 // get probe points
229 float t1x, t1y, t2x, t2y, t3x, t3y;
230 std::tie(t1x, t1y, t2x, t2y, t3x, t3y) = getCoordinates(this->probe_radius);
231
232 float trimx= 0.0F, trimy= 0.0F, trimz= 0.0F;
233 if(!keep) {
234 // zero trim values
235 if(!set_trim(0, 0, 0, gcode->stream)) return false;
236
237 }else{
238 // get current trim, and continue from that
239 if (get_trim(trimx, trimy, trimz)) {
240 gcode->stream->printf("Current Trim X: %f, Y: %f, Z: %f\r\n", trimx, trimy, trimz);
241
242 } else {
243 gcode->stream->printf("Could not get current trim, are endstops enabled?\n");
244 return false;
245 }
246 }
247
248 // home
249 home();
250
251 // find bed, run at fast rate
252 int s;
253 if(!run_probe(s, true)) return false;
254
255 float bedht= s/Z_STEPS_PER_MM - this->probe_height; // distance to move from home to 5mm above bed
256 gcode->stream->printf("Bed ht is %f mm\n", bedht);
257
258 // move to start position
259 home();
260 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // do a relative move from home to the point above the bed
261
262 // get initial probes
263 // probe the base of the X tower
264 if(!probe_delta_tower(s, t1x, t1y)) return false;
265 float t1z= s / Z_STEPS_PER_MM;
266 gcode->stream->printf("T1-0 Z:%1.4f C:%d\n", t1z, s);
267
268 // probe the base of the Y tower
269 if(!probe_delta_tower(s, t2x, t2y)) return false;
270 float t2z= s / Z_STEPS_PER_MM;
271 gcode->stream->printf("T2-0 Z:%1.4f C:%d\n", t2z, s);
272
273 // probe the base of the Z tower
274 if(!probe_delta_tower(s, t3x, t3y)) return false;
275 float t3z= s / Z_STEPS_PER_MM;
276 gcode->stream->printf("T3-0 Z:%1.4f C:%d\n", t3z, s);
277
278 float trimscale= 1.2522F; // empirically determined
279
280 auto mm= std::minmax({t1z, t2z, t3z});
281 if((mm.second-mm.first) <= target) {
282 gcode->stream->printf("trim already set within required parameters: delta %f\n", mm.second-mm.first);
283 return true;
284 }
285
286 // set trims to worst case so we always have a negative trim
287 trimx += (mm.first-t1z)*trimscale;
288 trimy += (mm.first-t2z)*trimscale;
289 trimz += (mm.first-t3z)*trimscale;
290
291 for (int i = 1; i <= 10; ++i) {
292 // set trim
293 if(!set_trim(trimx, trimy, trimz, gcode->stream)) return false;
294
295 // home and move probe to start position just above the bed
296 home();
297 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // do a relative move from home to the point above the bed
298
299 // probe the base of the X tower
300 if(!probe_delta_tower(s, t1x, t1y)) return false;
301 t1z= s / Z_STEPS_PER_MM;
302 gcode->stream->printf("T1-%d Z:%1.4f C:%d\n", i, t1z, s);
303
304 // probe the base of the Y tower
305 if(!probe_delta_tower(s, t2x, t2y)) return false;
306 t2z= s / Z_STEPS_PER_MM;
307 gcode->stream->printf("T2-%d Z:%1.4f C:%d\n", i, t2z, s);
308
309 // probe the base of the Z tower
310 if(!probe_delta_tower(s, t3x, t3y)) return false;
311 t3z= s / Z_STEPS_PER_MM;
312 gcode->stream->printf("T3-%d Z:%1.4f C:%d\n", i, t3z, s);
313
314 mm= std::minmax({t1z, t2z, t3z});
315 if((mm.second-mm.first) <= target) {
316 gcode->stream->printf("trim set to within required parameters: delta %f\n", mm.second-mm.first);
317 break;
318 }
319
320 // set new trim values based on min difference
321 trimx += (mm.first-t1z)*trimscale;
322 trimy += (mm.first-t2z)*trimscale;
323 trimz += (mm.first-t3z)*trimscale;
324
325 // flush the output
326 THEKERNEL->call_event(ON_IDLE);
327 }
328
329 if((mm.second-mm.first) > target) {
330 gcode->stream->printf("WARNING: trim did not resolve to within required parameters: delta %f\n", mm.second-mm.first);
331 }
332
333 return true;
334 }
335
336 /*
337 probe edges to get outer positions, then probe center
338 modify the delta radius until center and X converge
339 */
340
341 bool ZProbe::calibrate_delta_radius(Gcode *gcode)
342 {
343 float target= 0.03F;
344 if(gcode->has_letter('I')) target= gcode->get_value('I'); // override default target
345 if(gcode->has_letter('J')) this->probe_radius= gcode->get_value('J'); // override default probe radius
346
347 gcode->stream->printf("Calibrating delta radius: target %f, radius %f\n", target, this->probe_radius);
348
349 // get probe points
350 float t1x, t1y, t2x, t2y, t3x, t3y;
351 std::tie(t1x, t1y, t2x, t2y, t3x, t3y) = getCoordinates(this->probe_radius);
352
353 home();
354 // find bed, then move to a point 5mm above it
355 int s;
356 if(!run_probe(s, true)) return false;
357 float bedht= s/Z_STEPS_PER_MM - this->probe_height; // distance to move from home to 5mm above bed
358 gcode->stream->printf("Bed ht is %f mm\n", bedht);
359
360 home();
361 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // do a relative move from home to the point above the bed
362
363 // probe center to get reference point at this Z height
364 int dc;
365 if(!probe_delta_tower(dc, 0, 0)) return false;
366 gcode->stream->printf("CT Z:%1.3f C:%d\n", dc / Z_STEPS_PER_MM, dc);
367 float cmm= dc / Z_STEPS_PER_MM;
368
369 // get current delta radius
370 float delta_radius= 0.0F;
371 BaseSolution::arm_options_t options;
372 if(THEKERNEL->robot->arm_solution->get_optional(options)) {
373 delta_radius= options['R'];
374 }
375 if(delta_radius == 0.0F) {
376 gcode->stream->printf("This appears to not be a delta arm solution\n");
377 return false;
378 }
379 options.clear();
380
381 float drinc= 2.5F; // approx
382 for (int i = 1; i <= 10; ++i) {
383 // probe t1, t2, t3 and get average, but use coordinated moves, probing center won't change
384 int dx, dy, dz;
385 if(!probe_delta_tower(dx, t1x, t1y)) return false;
386 gcode->stream->printf("T1-%d Z:%1.3f C:%d\n", i, dx / Z_STEPS_PER_MM, dx);
387 if(!probe_delta_tower(dy, t2x, t2y)) return false;
388 gcode->stream->printf("T2-%d Z:%1.3f C:%d\n", i, dy / Z_STEPS_PER_MM, dy);
389 if(!probe_delta_tower(dz, t3x, t3y)) return false;
390 gcode->stream->printf("T3-%d Z:%1.3f C:%d\n", i, dz / Z_STEPS_PER_MM, dz);
391
392 // now look at the difference and reduce it by adjusting delta radius
393 float m= ((dx+dy+dz)/3.0F) / Z_STEPS_PER_MM;
394 float d= cmm-m;
395 gcode->stream->printf("C-%d Z-ave:%1.4f delta: %1.3f\n", i, m, d);
396
397 if(abs(d) <= target) break; // resolution of success
398
399 // increase delta radius to adjust for low center
400 // decrease delta radius to adjust for high center
401 delta_radius += (d*drinc);
402
403 // set the new delta radius
404 options['R']= delta_radius;
405 THEKERNEL->robot->arm_solution->set_optional(options);
406 gcode->stream->printf("Setting delta radius to: %1.4f\n", delta_radius);
407
408 home();
409 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // needs to be a relative coordinated move
410
411 // flush the output
412 THEKERNEL->call_event(ON_IDLE);
413 }
414 return true;
415 }
416
417 void ZProbe::on_gcode_received(void *argument)
418 {
419 Gcode *gcode = static_cast<Gcode *>(argument);
420
421 if( gcode->has_g) {
422 // G code processing
423 if( gcode->g == 30 ) { // simple Z probe
424 gcode->mark_as_taken();
425 // first wait for an empty queue i.e. no moves left
426 THEKERNEL->conveyor->wait_for_empty_queue();
427
428 // make sure the probe is not already triggered before moving motors
429 if(this->pin.get()) {
430 gcode->stream->printf("ZProbe triggered before move, aborting command.\n");
431 return;
432 }
433
434 int steps;
435 if(run_probe(steps)) {
436 gcode->stream->printf("Z:%1.4f C:%d\n", steps / Z_STEPS_PER_MM, steps);
437 // move back to where it started, unless a Z is specified
438 if(gcode->has_letter('Z')) {
439 // set Z to the specified value, and leave probe where it is
440 THEKERNEL->robot->reset_axis_position(gcode->get_value('Z'), Z_AXIS);
441 } else {
442 return_probe(steps);
443 }
444 } else {
445 gcode->stream->printf("ZProbe not triggered\n");
446 }
447
448 } else if( gcode->g == 32 ) { // auto calibration for delta, Z bed mapping for cartesian
449 // first wait for an empty queue i.e. no moves left
450 THEKERNEL->conveyor->wait_for_empty_queue();
451 gcode->mark_as_taken();
452
453 // make sure the probe is not already triggered before moving motors
454 if(this->pin.get()) {
455 gcode->stream->printf("ZProbe triggered before move, aborting command.\n");
456 return;
457 }
458
459 if(is_delta) {
460 if(!gcode->has_letter('R')){
461 if(!calibrate_delta_endstops(gcode)) {
462 gcode->stream->printf("Calibration failed to complete, probe not triggered\n");
463 return;
464 }
465 }
466 if(!gcode->has_letter('E')){
467 if(!calibrate_delta_radius(gcode)) {
468 gcode->stream->printf("Calibration failed to complete, probe not triggered\n");
469 return;
470 }
471 }
472 gcode->stream->printf("Calibration complete, save settings with M500\n");
473
474 } else {
475 // TODO create Z height map for bed
476 gcode->stream->printf("Not supported yet\n");
477 }
478 }
479
480 } else if(gcode->has_m) {
481 // M code processing here
482 if(gcode->m == 119) {
483 int c = this->pin.get();
484 gcode->stream->printf(" Probe: %d", c);
485 gcode->add_nl = true;
486 gcode->mark_as_taken();
487
488 } else if (gcode->m == 557) { // P0 Xxxx Yyyy sets probe points for G32
489 // TODO will override the automatically calculated probe points for a delta, required for a cartesian
490
491 gcode->mark_as_taken();
492 }
493 }
494 }
495
496 #define max(a,b) (((a) > (b)) ? (a) : (b))
497 // Called periodically to change the speed to match acceleration
498 uint32_t ZProbe::acceleration_tick(uint32_t dummy)
499 {
500 if(!this->running) return(0); // nothing to do
501
502 // foreach stepper that is moving
503 for ( int c = X_AXIS; c <= Z_AXIS; c++ ) {
504 if( !STEPPER[c]->is_moving() ) continue;
505
506 uint32_t current_rate = STEPPER[c]->get_steps_per_second();
507 uint32_t target_rate = int(floor(this->current_feedrate));
508
509 if( current_rate < target_rate ) {
510 uint32_t rate_increase = int(floor((THEKERNEL->planner->get_acceleration() / THEKERNEL->stepper->get_acceleration_ticks_per_second()) * STEPS_PER_MM(c)));
511 current_rate = min( target_rate, current_rate + rate_increase );
512 }
513 if( current_rate > target_rate ) {
514 current_rate = target_rate;
515 }
516
517 // steps per second
518 STEPPER[c]->set_speed(max(current_rate, THEKERNEL->stepper->get_minimum_steps_per_second()));
519 }
520
521 return 0;
522 }
523
524 // issue a coordinated move directly to robot, and return when done
525 // Only move the coordinates that are passed in as not nan
526 void ZProbe::coordinated_move(float x, float y, float z, float feedrate, bool relative)
527 {
528 char buf[32];
529 char cmd[64];
530
531 if(relative) strcpy(cmd, "G91 G0 ");
532 else strcpy(cmd, "G0 ");
533
534 if(!isnan(x)) {
535 int n = snprintf(buf, sizeof(buf), " X%1.3f", x);
536 strncat(cmd, buf, n);
537 }
538 if(!isnan(y)) {
539 int n = snprintf(buf, sizeof(buf), " Y%1.3f", y);
540 strncat(cmd, buf, n);
541 }
542 if(!isnan(z)) {
543 int n = snprintf(buf, sizeof(buf), " Z%1.3f", z);
544 strncat(cmd, buf, n);
545 }
546
547 // use specified feedrate (mm/sec)
548 int n = snprintf(buf, sizeof(buf), " F%1.1f", feedrate * 60); // feed rate is converted to mm/min
549 strncat(cmd, buf, n);
550 if(relative) strcat(cmd, " G90");
551
552 //THEKERNEL->streams->printf("DEBUG: move: %s\n", cmd);
553
554 // send as a command line as may have multiple G codes in it
555 struct SerialMessage message;
556 message.message = cmd;
557 message.stream = &(StreamOutput::NullStream);
558 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
559 THEKERNEL->conveyor->wait_for_empty_queue();
560 }
561
562 // issue home command
563 void ZProbe::home()
564 {
565 Gcode gc("G28", &(StreamOutput::NullStream));
566 THEKERNEL->call_event(ON_GCODE_RECEIVED, &gc);
567 }
568
569 bool ZProbe::set_trim(float x, float y, float z, StreamOutput *stream)
570 {
571 float t[3]{x, y, z};
572 bool ok= PublicData::set_value( endstops_checksum, trim_checksum, t);
573
574 if (ok) {
575 stream->printf("set trim to X:%f Y:%f Z:%f\n", x, y, z);
576 } else {
577 stream->printf("unable to set trim, is endstops enabled?\n");
578 }
579
580 return ok;
581 }
582
583 bool ZProbe::get_trim(float& x, float& y, float& z)
584 {
585 void *returned_data;
586 bool ok = PublicData::get_value( endstops_checksum, trim_checksum, &returned_data );
587
588 if (ok) {
589 float *trim = static_cast<float *>(returned_data);
590 x= trim[0];
591 y= trim[1];
592 z= trim[2];
593 return true;
594 }
595 return false;
596 }