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/>.
11 #include "BaseSolution.h"
14 #include "StepperMotor.h"
15 #include "StreamOutputPool.h"
19 #include "checksumm.h"
20 #include "ConfigValue.h"
21 #include "SlowTicker.h"
23 #include "SerialMessage.h"
24 #include "PublicDataRequest.h"
25 #include "EndstopsPublicAccess.h"
26 #include "PublicData.h"
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")
40 // from endstop section
41 #define delta_homing_checksum CHECKSUM("delta_homing")
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)
51 #define abs(a) ((a<0) ? -a : a)
53 void ZProbe::on_module_loaded()
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
61 this->running
= false;
64 this->on_config_reload(this);
65 // register event-handlers
66 register_for_event(ON_GCODE_RECEIVED
);
67 register_for_event(ON_IDLE
);
69 THEKERNEL
->slow_ticker
->attach( THEKERNEL
->stepper
->get_acceleration_ticks_per_second() , this, &ZProbe::acceleration_tick
);
72 void ZProbe::on_config_reload(void *argument
)
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();
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();
80 // default is probably wrong
81 this->probe_radius
= THEKERNEL
->config
->value(zprobe_checksum
, probe_radius_checksum
)->by_default(100.0F
)->as_number();
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
89 bool ZProbe::wait_for_probe(int steps
[3])
91 unsigned int debounce
= 0;
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() ) {
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
106 // ...otherwise stop the steppers, return its remaining steps
107 for( int i
= X_AXIS
; i
<= Z_AXIS
; i
++ ) {
109 if ( STEPPER
[i
]->is_moving() ) {
110 steps
[i
] = STEPPER
[i
]->get_stepped();
111 STEPPER
[i
]->move(0, 0);
117 // The probe was not hit yet, reset debounce counter
123 void ZProbe::on_idle(void *argument
)
127 // single probe and report amount moved
128 bool ZProbe::run_probe(int& steps
, bool fast
)
131 THEKERNEL
->stepper
->turn_enable_pins_on();
132 this->current_feedrate
= (fast
? this->fast_feedrate
: this->slow_feedrate
) * Z_STEPS_PER_MM
; // steps/sec
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
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
));
145 this->running
= true;
148 bool r
= wait_for_probe(s
);
149 steps
= s
[Z_AXIS
]; // only need z
150 this->running
= false;
154 bool ZProbe::return_probe(int steps
)
156 // move probe back to where it was
157 this->current_feedrate
= this->fast_feedrate
* Z_STEPS_PER_MM
; // feedrate in steps/sec
161 STEPPER
[Z_AXIS
]->set_speed(0); // will be increased by acceleration tick
162 STEPPER
[Z_AXIS
]->move(dir
, steps
);
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
);
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
);
176 this->running
= false;
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
)
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
);
192 bool ZProbe::probe_delta_tower(int& steps
, float x
, float y
)
196 coordinated_move(x
, y
, NAN
, this->fast_feedrate
);
197 if(!run_probe(s
)) return false;
199 // return to original Z
206 /* Run a calibration routine for a delta
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
216 bool ZProbe::calibrate_delta_endstops(Gcode
*gcode
)
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
223 if(gcode
->has_letter('K')) keep
= true; // keep current settings
225 gcode
->stream
->printf("Calibrating Endstops: target %fmm, radius %fmm\n", target
, this->probe_radius
);
228 float t1x
, t1y
, t2x
, t2y
, t3x
, t3y
;
229 std::tie(t1x
, t1y
, t2x
, t2y
, t3x
, t3y
) = getCoordinates(this->probe_radius
);
231 float trimx
= 0.0F
, trimy
= 0.0F
, trimz
= 0.0F
;
234 if(!set_trim(0, 0, 0, gcode
->stream
)) return false;
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
);
242 gcode
->stream
->printf("Could not get current trim, are endstops enabled?\n");
250 // find bed, run at fast rate
252 if(!run_probe(s
, true)) return false;
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
);
257 // move to start position
259 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // do a relative move from home to the point above the bed
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
);
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
);
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
);
277 float trimscale
= 1.2522F
; // empirically determined
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
);
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
;
290 for (int i
= 1; i
<= 10; ++i
) {
292 if(!set_trim(trimx
, trimy
, trimz
, gcode
->stream
)) return false;
294 // home and move probe to start position just above the bed
296 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // do a relative move from home to the point above the bed
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
);
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
);
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
);
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
);
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
;
325 THEKERNEL
->call_event(ON_IDLE
);
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
);
336 probe edges to get outer positions, then probe center
337 modify the delta radius until center and X converge
340 bool ZProbe::calibrate_delta_radius(Gcode
*gcode
)
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
346 gcode
->stream
->printf("Calibrating delta radius: target %f, radius %f\n", target
, this->probe_radius
);
349 float t1x
, t1y
, t2x
, t2y
, t3x
, t3y
;
350 std::tie(t1x
, t1y
, t2x
, t2y
, t3x
, t3y
) = getCoordinates(this->probe_radius
);
353 // find bed, then move to a point 5mm above it
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
);
360 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // do a relative move from home to the point above the bed
362 // probe center to get reference point at this Z height
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
;
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'];
374 if(delta_radius
== 0.0F
) {
375 gcode
->stream
->printf("This appears to not be a delta arm solution\n");
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
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
);
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
;
394 gcode
->stream
->printf("C-%d Z-ave:%1.4f delta: %1.3f\n", i
, m
, d
);
396 if(abs(d
) <= target
) break; // resolution of success
398 // increase delta radius to adjust for low center
399 // decrease delta radius to adjust for high center
400 delta_radius
+= (d
*drinc
);
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
);
408 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // needs to be a relative coordinated move
411 THEKERNEL
->call_event(ON_IDLE
);
416 void ZProbe::on_gcode_received(void *argument
)
418 Gcode
*gcode
= static_cast<Gcode
*>(argument
);
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();
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");
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
);
444 gcode
->stream
->printf("ZProbe not triggered\n");
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();
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");
459 if(!gcode
->has_letter('R')){
460 if(!calibrate_delta_endstops(gcode
)) {
461 gcode
->stream
->printf("Calibration failed to complete, probe not triggered\n");
465 if(!gcode
->has_letter('E')){
466 if(!calibrate_delta_radius(gcode
)) {
467 gcode
->stream
->printf("Calibration failed to complete, probe not triggered\n");
471 gcode
->stream
->printf("Calibration complete, save settings with M500\n");
474 // TODO create Z height map for bed
475 gcode
->stream
->printf("Not supported yet\n");
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();
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
490 gcode
->mark_as_taken();
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
)
499 if(!this->running
) return(0); // nothing to do
501 // foreach stepper that is moving
502 for ( int c
= X_AXIS
; c
<= Z_AXIS
; c
++ ) {
503 if( !STEPPER
[c
]->is_moving() ) continue;
505 uint32_t current_rate
= STEPPER
[c
]->get_steps_per_second();
506 uint32_t target_rate
= int(floor(this->current_feedrate
));
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
);
512 if( current_rate
> target_rate
) {
513 current_rate
= target_rate
;
517 STEPPER
[c
]->set_speed(max(current_rate
, THEKERNEL
->stepper
->get_minimum_steps_per_second()));
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
)
530 if(relative
) strcpy(cmd
, "G91 G0 ");
531 else strcpy(cmd
, "G0 ");
534 int n
= snprintf(buf
, sizeof(buf
), " X%1.3f", x
);
535 strncat(cmd
, buf
, n
);
538 int n
= snprintf(buf
, sizeof(buf
), " Y%1.3f", y
);
539 strncat(cmd
, buf
, n
);
542 int n
= snprintf(buf
, sizeof(buf
), " Z%1.3f", z
);
543 strncat(cmd
, buf
, n
);
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");
551 //THEKERNEL->streams->printf("DEBUG: move: %s\n", cmd);
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();
561 // issue home command
564 Gcode
gc("G28", &(StreamOutput::NullStream
));
565 THEKERNEL
->call_event(ON_GCODE_RECEIVED
, &gc
);
568 bool ZProbe::set_trim(float x
, float y
, float z
, StreamOutput
*stream
)
571 bool ok
= PublicData::set_value( endstops_checksum
, trim_checksum
, t
);
574 stream
->printf("set trim to X:%f Y:%f Z:%f\n", x
, y
, z
);
576 stream
->printf("unable to set trim, is endstops enabled?\n");
582 bool ZProbe::get_trim(float& x
, float& y
, float& z
)
585 bool ok
= PublicData::get_value( endstops_checksum
, trim_checksum
, &returned_data
);
588 float *trim
= static_cast<float *>(returned_data
);