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