c8f848a5a8b643a8d88b33905fbfedcb9e9a11c2
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_CONFIG_RELOAD
);
67 register_for_event(ON_GCODE_RECEIVED
);
68 register_for_event(ON_IDLE
);
70 THEKERNEL
->slow_ticker
->attach( THEKERNEL
->stepper
->get_acceleration_ticks_per_second() , this, &ZProbe::acceleration_tick
);
73 void ZProbe::on_config_reload(void *argument
)
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();
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();
81 // default is probably wrong
82 this->probe_radius
= THEKERNEL
->config
->value(zprobe_checksum
, probe_radius_checksum
)->by_default(100.0F
)->as_number();
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
90 bool ZProbe::wait_for_probe(int steps
[3])
92 unsigned int debounce
= 0;
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() ) {
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
107 // ...otherwise stop the steppers, return its remaining steps
108 for( int i
= X_AXIS
; i
<= Z_AXIS
; i
++ ) {
110 if ( STEPPER
[i
]->is_moving() ) {
111 steps
[i
] = STEPPER
[i
]->get_stepped();
112 STEPPER
[i
]->move(0, 0);
118 // The probe was not hit yet, reset debounce counter
124 void ZProbe::on_idle(void *argument
)
128 // single probe and report amount moved
129 bool ZProbe::run_probe(int& steps
, bool fast
)
132 THEKERNEL
->stepper
->turn_enable_pins_on();
133 this->current_feedrate
= (fast
? this->fast_feedrate
: this->slow_feedrate
) * Z_STEPS_PER_MM
; // steps/sec
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
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
));
146 this->running
= true;
149 bool r
= wait_for_probe(s
);
150 steps
= s
[Z_AXIS
]; // only need z
151 this->running
= false;
155 bool ZProbe::return_probe(int steps
)
157 // move probe back to where it was
158 this->current_feedrate
= this->fast_feedrate
* Z_STEPS_PER_MM
; // feedrate in steps/sec
162 STEPPER
[Z_AXIS
]->set_speed(0); // will be increased by acceleration tick
163 STEPPER
[Z_AXIS
]->move(dir
, steps
);
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
);
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
);
177 this->running
= false;
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
)
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
);
193 bool ZProbe::probe_delta_tower(int& steps
, float x
, float y
)
197 coordinated_move(x
, y
, NAN
, this->fast_feedrate
);
198 if(!run_probe(s
)) return false;
200 // return to original Z
207 /* Run a calibration routine for a delta
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
217 bool ZProbe::calibrate_delta_endstops(Gcode
*gcode
)
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
224 if(gcode
->has_letter('K')) keep
= true; // keep current settings
226 gcode
->stream
->printf("Calibrating Endstops: target %fmm, radius %fmm\n", target
, this->probe_radius
);
229 float t1x
, t1y
, t2x
, t2y
, t3x
, t3y
;
230 std::tie(t1x
, t1y
, t2x
, t2y
, t3x
, t3y
) = getCoordinates(this->probe_radius
);
232 float trimx
= 0.0F
, trimy
= 0.0F
, trimz
= 0.0F
;
235 if(!set_trim(0, 0, 0, gcode
->stream
)) return false;
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
);
243 gcode
->stream
->printf("Could not get current trim, are endstops enabled?\n");
251 // find bed, run at fast rate
253 if(!run_probe(s
, true)) return false;
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
);
258 // move to start position
260 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // do a relative move from home to the point above the bed
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
);
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
);
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
);
278 float trimscale
= 1.2522F
; // empirically determined
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
);
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
;
291 for (int i
= 1; i
<= 10; ++i
) {
293 if(!set_trim(trimx
, trimy
, trimz
, gcode
->stream
)) return false;
295 // home and move probe to start position just above the bed
297 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // do a relative move from home to the point above the bed
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
);
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
);
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
);
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
);
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
;
326 THEKERNEL
->call_event(ON_IDLE
);
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
);
337 probe edges to get outer positions, then probe center
338 modify the delta radius until center and X converge
341 bool ZProbe::calibrate_delta_radius(Gcode
*gcode
)
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
347 gcode
->stream
->printf("Calibrating delta radius: target %f, radius %f\n", target
, this->probe_radius
);
350 float t1x
, t1y
, t2x
, t2y
, t3x
, t3y
;
351 std::tie(t1x
, t1y
, t2x
, t2y
, t3x
, t3y
) = getCoordinates(this->probe_radius
);
354 // find bed, then move to a point 5mm above it
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
);
361 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // do a relative move from home to the point above the bed
363 // probe center to get reference point at this Z height
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
;
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'];
375 if(delta_radius
== 0.0F
) {
376 gcode
->stream
->printf("This appears to not be a delta arm solution\n");
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
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
);
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
;
395 gcode
->stream
->printf("C-%d Z-ave:%1.4f delta: %1.3f\n", i
, m
, d
);
397 if(abs(d
) <= target
) break; // resolution of success
399 // increase delta radius to adjust for low center
400 // decrease delta radius to adjust for high center
401 delta_radius
+= (d
*drinc
);
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
);
409 coordinated_move(NAN
, NAN
, -bedht
, this->fast_feedrate
, true); // needs to be a relative coordinated move
412 THEKERNEL
->call_event(ON_IDLE
);
417 void ZProbe::on_gcode_received(void *argument
)
419 Gcode
*gcode
= static_cast<Gcode
*>(argument
);
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();
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");
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
);
445 gcode
->stream
->printf("ZProbe not triggered\n");
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();
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");
460 if(!gcode
->has_letter('R')){
461 if(!calibrate_delta_endstops(gcode
)) {
462 gcode
->stream
->printf("Calibration failed to complete, probe not triggered\n");
466 if(!gcode
->has_letter('E')){
467 if(!calibrate_delta_radius(gcode
)) {
468 gcode
->stream
->printf("Calibration failed to complete, probe not triggered\n");
472 gcode
->stream
->printf("Calibration complete, save settings with M500\n");
475 // TODO create Z height map for bed
476 gcode
->stream
->printf("Not supported yet\n");
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();
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
491 gcode
->mark_as_taken();
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
)
500 if(!this->running
) return(0); // nothing to do
502 // foreach stepper that is moving
503 for ( int c
= X_AXIS
; c
<= Z_AXIS
; c
++ ) {
504 if( !STEPPER
[c
]->is_moving() ) continue;
506 uint32_t current_rate
= STEPPER
[c
]->get_steps_per_second();
507 uint32_t target_rate
= int(floor(this->current_feedrate
));
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
);
513 if( current_rate
> target_rate
) {
514 current_rate
= target_rate
;
518 STEPPER
[c
]->set_speed(max(current_rate
, THEKERNEL
->stepper
->get_minimum_steps_per_second()));
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
)
531 if(relative
) strcpy(cmd
, "G91 G0 ");
532 else strcpy(cmd
, "G0 ");
535 int n
= snprintf(buf
, sizeof(buf
), " X%1.3f", x
);
536 strncat(cmd
, buf
, n
);
539 int n
= snprintf(buf
, sizeof(buf
), " Y%1.3f", y
);
540 strncat(cmd
, buf
, n
);
543 int n
= snprintf(buf
, sizeof(buf
), " Z%1.3f", z
);
544 strncat(cmd
, buf
, n
);
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");
552 //THEKERNEL->streams->printf("DEBUG: move: %s\n", cmd);
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();
562 // issue home command
565 Gcode
gc("G28", &(StreamOutput::NullStream
));
566 THEKERNEL
->call_event(ON_GCODE_RECEIVED
, &gc
);
569 bool ZProbe::set_trim(float x
, float y
, float z
, StreamOutput
*stream
)
572 bool ok
= PublicData::set_value( endstops_checksum
, trim_checksum
, t
);
575 stream
->printf("set trim to X:%f Y:%f Z:%f\n", x
, y
, z
);
577 stream
->printf("unable to set trim, is endstops enabled?\n");
583 bool ZProbe::get_trim(float& x
, float& y
, float& z
)
586 bool ok
= PublicData::get_value( endstops_checksum
, trim_checksum
, &returned_data
);
589 float *trim
= static_cast<float *>(returned_data
);