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