refactor get public data, to be slightly easier to use on callee side
[clinton/Smoothieware.git] / src / modules / robot / Robot.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) with additions from Sungeun K. Jeon (https://github.com/chamnit/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 "libs/Module.h"
9 #include "libs/Kernel.h"
10
11 #include "mbed.h" // for us_ticker_read()
12
13 #include <math.h>
14 #include <string>
15 using std::string;
16
17 #include "Planner.h"
18 #include "Conveyor.h"
19 #include "Robot.h"
20 #include "nuts_bolts.h"
21 #include "Pin.h"
22 #include "StepperMotor.h"
23 #include "Gcode.h"
24 #include "PublicDataRequest.h"
25 #include "PublicData.h"
26 #include "arm_solutions/BaseSolution.h"
27 #include "arm_solutions/CartesianSolution.h"
28 #include "arm_solutions/RotatableCartesianSolution.h"
29 #include "arm_solutions/LinearDeltaSolution.h"
30 #include "arm_solutions/RotatableDeltaSolution.h"
31 #include "arm_solutions/HBotSolution.h"
32 #include "arm_solutions/MorganSCARASolution.h"
33 #include "StepTicker.h"
34 #include "checksumm.h"
35 #include "utils.h"
36 #include "ConfigValue.h"
37 #include "libs/StreamOutput.h"
38 #include "StreamOutputPool.h"
39 #include "ExtruderPublicAccess.h"
40
41 #define default_seek_rate_checksum CHECKSUM("default_seek_rate")
42 #define default_feed_rate_checksum CHECKSUM("default_feed_rate")
43 #define mm_per_line_segment_checksum CHECKSUM("mm_per_line_segment")
44 #define delta_segments_per_second_checksum CHECKSUM("delta_segments_per_second")
45 #define mm_per_arc_segment_checksum CHECKSUM("mm_per_arc_segment")
46 #define arc_correction_checksum CHECKSUM("arc_correction")
47 #define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
48 #define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
49 #define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
50
51 // arm solutions
52 #define arm_solution_checksum CHECKSUM("arm_solution")
53 #define cartesian_checksum CHECKSUM("cartesian")
54 #define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
55 #define rostock_checksum CHECKSUM("rostock")
56 #define linear_delta_checksum CHECKSUM("linear_delta")
57 #define rotatable_delta_checksum CHECKSUM("rotatable_delta")
58 #define delta_checksum CHECKSUM("delta")
59 #define hbot_checksum CHECKSUM("hbot")
60 #define corexy_checksum CHECKSUM("corexy")
61 #define kossel_checksum CHECKSUM("kossel")
62 #define morgan_checksum CHECKSUM("morgan")
63
64 // stepper motor stuff
65 #define alpha_step_pin_checksum CHECKSUM("alpha_step_pin")
66 #define beta_step_pin_checksum CHECKSUM("beta_step_pin")
67 #define gamma_step_pin_checksum CHECKSUM("gamma_step_pin")
68 #define alpha_dir_pin_checksum CHECKSUM("alpha_dir_pin")
69 #define beta_dir_pin_checksum CHECKSUM("beta_dir_pin")
70 #define gamma_dir_pin_checksum CHECKSUM("gamma_dir_pin")
71 #define alpha_en_pin_checksum CHECKSUM("alpha_en_pin")
72 #define beta_en_pin_checksum CHECKSUM("beta_en_pin")
73 #define gamma_en_pin_checksum CHECKSUM("gamma_en_pin")
74
75 #define alpha_steps_per_mm_checksum CHECKSUM("alpha_steps_per_mm")
76 #define beta_steps_per_mm_checksum CHECKSUM("beta_steps_per_mm")
77 #define gamma_steps_per_mm_checksum CHECKSUM("gamma_steps_per_mm")
78
79 #define alpha_max_rate_checksum CHECKSUM("alpha_max_rate")
80 #define beta_max_rate_checksum CHECKSUM("beta_max_rate")
81 #define gamma_max_rate_checksum CHECKSUM("gamma_max_rate")
82
83
84 // new-style actuator stuff
85 #define actuator_checksum CHEKCSUM("actuator")
86
87 #define step_pin_checksum CHECKSUM("step_pin")
88 #define dir_pin_checksum CHEKCSUM("dir_pin")
89 #define en_pin_checksum CHECKSUM("en_pin")
90
91 #define steps_per_mm_checksum CHECKSUM("steps_per_mm")
92 #define max_rate_checksum CHECKSUM("max_rate")
93
94 #define alpha_checksum CHECKSUM("alpha")
95 #define beta_checksum CHECKSUM("beta")
96 #define gamma_checksum CHECKSUM("gamma")
97
98 #define NEXT_ACTION_DEFAULT 0
99 #define NEXT_ACTION_DWELL 1
100 #define NEXT_ACTION_GO_HOME 2
101
102 #define MOTION_MODE_SEEK 0 // G0
103 #define MOTION_MODE_LINEAR 1 // G1
104 #define MOTION_MODE_CW_ARC 2 // G2
105 #define MOTION_MODE_CCW_ARC 3 // G3
106 #define MOTION_MODE_CANCEL 4 // G80
107
108 #define PATH_CONTROL_MODE_EXACT_PATH 0
109 #define PATH_CONTROL_MODE_EXACT_STOP 1
110 #define PATH_CONTROL_MODE_CONTINOUS 2
111
112 #define PROGRAM_FLOW_RUNNING 0
113 #define PROGRAM_FLOW_PAUSED 1
114 #define PROGRAM_FLOW_COMPLETED 2
115
116 #define SPINDLE_DIRECTION_CW 0
117 #define SPINDLE_DIRECTION_CCW 1
118
119 #define ARC_ANGULAR_TRAVEL_EPSILON 5E-7 // Float (radians)
120
121 // The Robot converts GCodes into actual movements, and then adds them to the Planner, which passes them to the Conveyor so they can be added to the queue
122 // It takes care of cutting arcs into segments, same thing for line that are too long
123
124 Robot::Robot()
125 {
126 this->inch_mode = false;
127 this->absolute_mode = true;
128 this->motion_mode = MOTION_MODE_SEEK;
129 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
130 clear_vector(this->last_milestone);
131 clear_vector(this->transformed_last_milestone);
132 this->arm_solution = NULL;
133 seconds_per_minute = 60.0F;
134 this->clearToolOffset();
135 this->compensationTransform= nullptr;
136 this->halted= false;
137 }
138
139 //Called when the module has just been loaded
140 void Robot::on_module_loaded()
141 {
142 this->register_for_event(ON_GCODE_RECEIVED);
143 this->register_for_event(ON_GET_PUBLIC_DATA);
144 this->register_for_event(ON_SET_PUBLIC_DATA);
145 this->register_for_event(ON_HALT);
146
147 // Configuration
148 this->on_config_reload(this);
149 }
150
151 void Robot::on_config_reload(void *argument)
152 {
153
154 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
155 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
156 // To make adding those solution easier, they have their own, separate object.
157 // Here we read the config to find out which arm solution to use
158 if (this->arm_solution) delete this->arm_solution;
159 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
160 // Note checksums are not const expressions when in debug mode, so don't use switch
161 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
162 this->arm_solution = new HBotSolution(THEKERNEL->config);
163
164 } else if(solution_checksum == rostock_checksum || solution_checksum == kossel_checksum || solution_checksum == delta_checksum || solution_checksum == linear_delta_checksum) {
165 this->arm_solution = new LinearDeltaSolution(THEKERNEL->config);
166
167 } else if(solution_checksum == rotatable_cartesian_checksum) {
168 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
169
170 } else if(solution_checksum == rotatable_delta_checksum) {
171 this->arm_solution = new RotatableDeltaSolution(THEKERNEL->config);
172
173
174 } else if(solution_checksum == morgan_checksum) {
175 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
176
177 } else if(solution_checksum == cartesian_checksum) {
178 this->arm_solution = new CartesianSolution(THEKERNEL->config);
179
180 } else {
181 this->arm_solution = new CartesianSolution(THEKERNEL->config);
182 }
183
184
185 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
186 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
187 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
188 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
189 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.5f)->as_number();
190 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
191
192 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
193 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
194 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
195
196 Pin alpha_step_pin;
197 Pin alpha_dir_pin;
198 Pin alpha_en_pin;
199 Pin beta_step_pin;
200 Pin beta_dir_pin;
201 Pin beta_en_pin;
202 Pin gamma_step_pin;
203 Pin gamma_dir_pin;
204 Pin gamma_en_pin;
205
206 alpha_step_pin.from_string( THEKERNEL->config->value(alpha_step_pin_checksum )->by_default("2.0" )->as_string())->as_output();
207 alpha_dir_pin.from_string( THEKERNEL->config->value(alpha_dir_pin_checksum )->by_default("0.5" )->as_string())->as_output();
208 alpha_en_pin.from_string( THEKERNEL->config->value(alpha_en_pin_checksum )->by_default("0.4" )->as_string())->as_output();
209 beta_step_pin.from_string( THEKERNEL->config->value(beta_step_pin_checksum )->by_default("2.1" )->as_string())->as_output();
210 beta_dir_pin.from_string( THEKERNEL->config->value(beta_dir_pin_checksum )->by_default("0.11" )->as_string())->as_output();
211 beta_en_pin.from_string( THEKERNEL->config->value(beta_en_pin_checksum )->by_default("0.10" )->as_string())->as_output();
212 gamma_step_pin.from_string( THEKERNEL->config->value(gamma_step_pin_checksum )->by_default("2.2" )->as_string())->as_output();
213 gamma_dir_pin.from_string( THEKERNEL->config->value(gamma_dir_pin_checksum )->by_default("0.20" )->as_string())->as_output();
214 gamma_en_pin.from_string( THEKERNEL->config->value(gamma_en_pin_checksum )->by_default("0.19" )->as_string())->as_output();
215
216 float steps_per_mm[3] = {
217 THEKERNEL->config->value(alpha_steps_per_mm_checksum)->by_default( 80.0F)->as_number(),
218 THEKERNEL->config->value(beta_steps_per_mm_checksum )->by_default( 80.0F)->as_number(),
219 THEKERNEL->config->value(gamma_steps_per_mm_checksum)->by_default(2560.0F)->as_number(),
220 };
221
222 // TODO: delete or detect old steppermotors
223 // Make our 3 StepperMotors
224 this->alpha_stepper_motor = new StepperMotor(alpha_step_pin, alpha_dir_pin, alpha_en_pin);
225 this->beta_stepper_motor = new StepperMotor(beta_step_pin, beta_dir_pin, beta_en_pin );
226 this->gamma_stepper_motor = new StepperMotor(gamma_step_pin, gamma_dir_pin, gamma_en_pin);
227
228 alpha_stepper_motor->change_steps_per_mm(steps_per_mm[0]);
229 beta_stepper_motor->change_steps_per_mm(steps_per_mm[1]);
230 gamma_stepper_motor->change_steps_per_mm(steps_per_mm[2]);
231
232 alpha_stepper_motor->set_max_rate(THEKERNEL->config->value(alpha_max_rate_checksum)->by_default(30000.0F)->as_number() / 60.0F);
233 beta_stepper_motor->set_max_rate(THEKERNEL->config->value(beta_max_rate_checksum )->by_default(30000.0F)->as_number() / 60.0F);
234 gamma_stepper_motor->set_max_rate(THEKERNEL->config->value(gamma_max_rate_checksum)->by_default(30000.0F)->as_number() / 60.0F);
235 check_max_actuator_speeds(); // check the configs are sane
236
237 actuators.clear();
238 actuators.push_back(alpha_stepper_motor);
239 actuators.push_back(beta_stepper_motor);
240 actuators.push_back(gamma_stepper_motor);
241
242
243 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
244 // so the first move can be correct if homing is not performed
245 float actuator_pos[3];
246 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
247 for (int i = 0; i < 3; i++)
248 actuators[i]->change_last_milestone(actuator_pos[i]);
249
250 //this->clearToolOffset();
251 }
252
253 // this does a sanity check that actuator speeds do not exceed steps rate capability
254 // we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
255 void Robot::check_max_actuator_speeds()
256 {
257 float step_freq= alpha_stepper_motor->get_max_rate() * alpha_stepper_motor->get_steps_per_mm();
258 if(step_freq > THEKERNEL->base_stepping_frequency) {
259 alpha_stepper_motor->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / alpha_stepper_motor->get_steps_per_mm()));
260 THEKERNEL->streams->printf("WARNING: alpha_max_rate exceeds base_stepping_frequency * alpha_steps_per_mm: %f, setting to %f\n", step_freq, alpha_stepper_motor->max_rate);
261 }
262
263 step_freq= beta_stepper_motor->get_max_rate() * beta_stepper_motor->get_steps_per_mm();
264 if(step_freq > THEKERNEL->base_stepping_frequency) {
265 beta_stepper_motor->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / beta_stepper_motor->get_steps_per_mm()));
266 THEKERNEL->streams->printf("WARNING: beta_max_rate exceeds base_stepping_frequency * beta_steps_per_mm: %f, setting to %f\n", step_freq, beta_stepper_motor->max_rate);
267 }
268
269 step_freq= gamma_stepper_motor->get_max_rate() * gamma_stepper_motor->get_steps_per_mm();
270 if(step_freq > THEKERNEL->base_stepping_frequency) {
271 gamma_stepper_motor->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / gamma_stepper_motor->get_steps_per_mm()));
272 THEKERNEL->streams->printf("WARNING: gamma_max_rate exceeds base_stepping_frequency * gamma_steps_per_mm: %f, setting to %f\n", step_freq, gamma_stepper_motor->max_rate);
273 }
274 }
275
276 void Robot::on_halt(void *arg)
277 {
278 halted= (arg == nullptr);
279 }
280
281 //A GCode has been received
282 //See if the current Gcode line has some orders for us
283 void Robot::on_gcode_received(void *argument)
284 {
285 Gcode *gcode = static_cast<Gcode *>(argument);
286
287 this->motion_mode = -1;
288
289 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
290 if( gcode->has_g) {
291 switch( gcode->g ) {
292 case 0: this->motion_mode = MOTION_MODE_SEEK; break;
293 case 1: this->motion_mode = MOTION_MODE_LINEAR; break;
294 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break;
295 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break;
296 case 4: {
297 uint32_t delay_ms= 0;
298 if (gcode->has_letter('P')) {
299 delay_ms= gcode->get_int('P');
300 }
301 if (gcode->has_letter('S')) {
302 delay_ms += gcode->get_int('S') * 1000;
303 }
304 if (delay_ms > 0){
305 // drain queue
306 THEKERNEL->conveyor->wait_for_empty_queue();
307 // wait for specified time
308 uint32_t start= us_ticker_read(); // mbed call
309 while ((us_ticker_read() - start) < delay_ms*1000) {
310 THEKERNEL->call_event(ON_IDLE, this);
311 }
312 }
313 }
314 break;
315 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
316 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
317 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
318 case 20: this->inch_mode = true; break;
319 case 21: this->inch_mode = false; break;
320 case 90: this->absolute_mode = true; break;
321 case 91: this->absolute_mode = false; break;
322 case 92: {
323 if(gcode->get_num_args() == 0) {
324 for (int i = X_AXIS; i <= Z_AXIS; ++i) {
325 reset_axis_position(0, i);
326 }
327
328 } else {
329 for (char letter = 'X'; letter <= 'Z'; letter++) {
330 if ( gcode->has_letter(letter) ) {
331 reset_axis_position(this->to_millimeters(gcode->get_value(letter)), letter - 'X');
332 }
333 }
334 }
335 return;
336 }
337 }
338 } else if( gcode->has_m) {
339 switch( gcode->m ) {
340 case 92: // M92 - set steps per mm
341 if (gcode->has_letter('X'))
342 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
343 if (gcode->has_letter('Y'))
344 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
345 if (gcode->has_letter('Z'))
346 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
347 if (gcode->has_letter('F'))
348 seconds_per_minute = gcode->get_value('F');
349
350 gcode->stream->printf("X:%g Y:%g Z:%g F:%g ", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm, seconds_per_minute);
351 gcode->add_nl = true;
352 check_max_actuator_speeds();
353 return;
354
355 case 114: {
356 char buf[64];
357 int n = snprintf(buf, sizeof(buf), "C: X:%1.3f Y:%1.3f Z:%1.3f A:%1.3f B:%1.3f C:%1.3f ",
358 from_millimeters(this->last_milestone[0]),
359 from_millimeters(this->last_milestone[1]),
360 from_millimeters(this->last_milestone[2]),
361 actuators[X_AXIS]->get_current_position(),
362 actuators[Y_AXIS]->get_current_position(),
363 actuators[Z_AXIS]->get_current_position() );
364 gcode->txt_after_ok.append(buf, n);
365 }
366 return;
367
368 case 120: { // push state
369 bool b= this->absolute_mode;
370 saved_state_t s(this->feed_rate, this->seek_rate, b);
371 state_stack.push(s);
372 }
373 break;
374
375 case 121: // pop state
376 if(!state_stack.empty()) {
377 auto s= state_stack.top();
378 state_stack.pop();
379 this->feed_rate= std::get<0>(s);
380 this->seek_rate= std::get<1>(s);
381 this->absolute_mode= std::get<2>(s);
382 }
383 break;
384
385 case 203: // M203 Set maximum feedrates in mm/sec
386 if (gcode->has_letter('X'))
387 this->max_speeds[X_AXIS] = gcode->get_value('X');
388 if (gcode->has_letter('Y'))
389 this->max_speeds[Y_AXIS] = gcode->get_value('Y');
390 if (gcode->has_letter('Z'))
391 this->max_speeds[Z_AXIS] = gcode->get_value('Z');
392 if (gcode->has_letter('A'))
393 alpha_stepper_motor->set_max_rate(gcode->get_value('A'));
394 if (gcode->has_letter('B'))
395 beta_stepper_motor->set_max_rate(gcode->get_value('B'));
396 if (gcode->has_letter('C'))
397 gamma_stepper_motor->set_max_rate(gcode->get_value('C'));
398
399 check_max_actuator_speeds();
400
401 if(gcode->get_num_args() == 0) {
402 gcode->stream->printf("X:%g Y:%g Z:%g A:%g B:%g C:%g ",
403 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS],
404 alpha_stepper_motor->get_max_rate(), beta_stepper_motor->get_max_rate(), gamma_stepper_motor->get_max_rate());
405 gcode->add_nl = true;
406 }
407
408 break;
409
410 case 204: // M204 Snnn - set acceleration to nnn, Znnn sets z acceleration
411 if (gcode->has_letter('S')) {
412 float acc = gcode->get_value('S'); // mm/s^2
413 // enforce minimum
414 if (acc < 1.0F)
415 acc = 1.0F;
416 THEKERNEL->planner->acceleration = acc;
417 }
418 if (gcode->has_letter('Z')) {
419 float acc = gcode->get_value('Z'); // mm/s^2
420 // enforce positive
421 if (acc < 0.0F)
422 acc = 0.0F;
423 THEKERNEL->planner->z_acceleration = acc;
424 }
425 break;
426
427 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
428 if (gcode->has_letter('X')) {
429 float jd = gcode->get_value('X');
430 // enforce minimum
431 if (jd < 0.0F)
432 jd = 0.0F;
433 THEKERNEL->planner->junction_deviation = jd;
434 }
435 if (gcode->has_letter('Z')) {
436 float jd = gcode->get_value('Z');
437 // enforce minimum, -1 disables it and uses regular junction deviation
438 if (jd < -1.0F)
439 jd = -1.0F;
440 THEKERNEL->planner->z_junction_deviation = jd;
441 }
442 if (gcode->has_letter('S')) {
443 float mps = gcode->get_value('S');
444 // enforce minimum
445 if (mps < 0.0F)
446 mps = 0.0F;
447 THEKERNEL->planner->minimum_planner_speed = mps;
448 }
449 if (gcode->has_letter('Y')) {
450 alpha_stepper_motor->default_minimum_actuator_rate = gcode->get_value('Y');
451 }
452 break;
453
454 case 220: // M220 - speed override percentage
455 if (gcode->has_letter('S')) {
456 float factor = gcode->get_value('S');
457 // enforce minimum 10% speed
458 if (factor < 10.0F)
459 factor = 10.0F;
460 // enforce maximum 10x speed
461 if (factor > 1000.0F)
462 factor = 1000.0F;
463
464 seconds_per_minute = 6000.0F / factor;
465 }else{
466 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
467 }
468 break;
469
470 case 400: // wait until all moves are done up to this point
471 THEKERNEL->conveyor->wait_for_empty_queue();
472 break;
473
474 case 500: // M500 saves some volatile settings to config override file
475 case 503: { // M503 just prints the settings
476 gcode->stream->printf(";Steps per unit:\nM92 X%1.5f Y%1.5f Z%1.5f\n", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm);
477 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", THEKERNEL->planner->acceleration, THEKERNEL->planner->z_acceleration);
478 gcode->stream->printf(";X- Junction Deviation, Z- Z junction deviation, S - Minimum Planner speed mm/sec:\nM205 X%1.5f Z%1.5f S%1.5f\n", THEKERNEL->planner->junction_deviation, THEKERNEL->planner->z_junction_deviation, THEKERNEL->planner->minimum_planner_speed);
479 gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f A%1.5f B%1.5f C%1.5f\n",
480 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS],
481 alpha_stepper_motor->get_max_rate(), beta_stepper_motor->get_max_rate(), gamma_stepper_motor->get_max_rate());
482
483 // get or save any arm solution specific optional values
484 BaseSolution::arm_options_t options;
485 if(arm_solution->get_optional(options) && !options.empty()) {
486 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
487 for(auto &i : options) {
488 gcode->stream->printf(" %c%1.4f", i.first, i.second);
489 }
490 gcode->stream->printf("\n");
491 }
492
493 break;
494 }
495
496 case 665: { // M665 set optional arm solution variables based on arm solution.
497 // the parameter args could be any letter each arm solution only accepts certain ones
498 BaseSolution::arm_options_t options= gcode->get_args();
499 options.erase('S'); // don't include the S
500 options.erase('U'); // don't include the U
501 if(options.size() > 0) {
502 // set the specified options
503 arm_solution->set_optional(options);
504 }
505 options.clear();
506 if(arm_solution->get_optional(options)) {
507 // foreach optional value
508 for(auto &i : options) {
509 // print all current values of supported options
510 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
511 gcode->add_nl = true;
512 }
513 }
514
515 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
516 this->delta_segments_per_second = gcode->get_value('S');
517 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
518
519 }else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
520 this->mm_per_line_segment = gcode->get_value('U');
521 this->delta_segments_per_second = 0;
522 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
523 }
524
525 break;
526 }
527 }
528 }
529
530 if( this->motion_mode < 0)
531 return;
532
533 //Get parameters
534 float target[3], offset[3];
535 clear_vector(offset);
536
537 memcpy(target, this->last_milestone, sizeof(target)); //default to last target
538
539 for(char letter = 'I'; letter <= 'K'; letter++) {
540 if( gcode->has_letter(letter) ) {
541 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
542 }
543 }
544 for(char letter = 'X'; letter <= 'Z'; letter++) {
545 if( gcode->has_letter(letter) ) {
546 target[letter - 'X'] = this->to_millimeters(gcode->get_value(letter)) + (this->absolute_mode ? this->toolOffset[letter - 'X'] : target[letter - 'X']);
547 }
548 }
549
550 if( gcode->has_letter('F') ) {
551 if( this->motion_mode == MOTION_MODE_SEEK )
552 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
553 else
554 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
555 }
556
557 //Perform any physical actions
558 switch(this->motion_mode) {
559 case MOTION_MODE_CANCEL: break;
560 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate / seconds_per_minute ); break;
561 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate / seconds_per_minute ); break;
562 case MOTION_MODE_CW_ARC:
563 case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
564 }
565
566 // last_milestone was set to target in append_milestone, no need to do it again
567
568 }
569
570 // We received a new gcode, and one of the functions
571 // determined the distance for that given gcode. So now we can attach this gcode to the right block
572 // and continue
573 void Robot::distance_in_gcode_is_known(Gcode *gcode)
574 {
575 //If the queue is empty, execute immediatly, otherwise attach to the last added block
576 THEKERNEL->conveyor->append_gcode(gcode);
577 }
578
579 // reset the position for all axis (used in homing for delta as last_milestone may be bogus)
580 void Robot::reset_axis_position(float x, float y, float z)
581 {
582 this->last_milestone[X_AXIS] = x;
583 this->last_milestone[Y_AXIS] = y;
584 this->last_milestone[Z_AXIS] = z;
585 this->transformed_last_milestone[X_AXIS] = x;
586 this->transformed_last_milestone[Y_AXIS] = y;
587 this->transformed_last_milestone[Z_AXIS] = z;
588
589 float actuator_pos[3];
590 arm_solution->cartesian_to_actuator(this->last_milestone, actuator_pos);
591 for (int i = 0; i < 3; i++)
592 actuators[i]->change_last_milestone(actuator_pos[i]);
593 }
594
595 // Reset the position for an axis (used in homing and G92)
596 void Robot::reset_axis_position(float position, int axis)
597 {
598 this->last_milestone[axis] = position;
599 this->transformed_last_milestone[axis] = position;
600
601 float actuator_pos[3];
602 arm_solution->cartesian_to_actuator(this->last_milestone, actuator_pos);
603
604 for (int i = 0; i < 3; i++)
605 actuators[i]->change_last_milestone(actuator_pos[i]);
606 }
607
608 // Use FK to find out where actuator is and reset lastmilestone to match
609 void Robot::reset_position_from_current_actuator_position()
610 {
611 float actuator_pos[]= {actuators[X_AXIS]->get_current_position(), actuators[Y_AXIS]->get_current_position(), actuators[Z_AXIS]->get_current_position()};
612 arm_solution->actuator_to_cartesian(actuator_pos, this->last_milestone);
613 memcpy(this->transformed_last_milestone, this->last_milestone, sizeof(this->transformed_last_milestone));
614
615 // now reset actuator correctly, NOTE this may lose a little precision
616 arm_solution->cartesian_to_actuator(this->last_milestone, actuator_pos);
617 for (int i = 0; i < 3; i++)
618 actuators[i]->change_last_milestone(actuator_pos[i]);
619 }
620
621 // Convert target from millimeters to steps, and append this to the planner
622 void Robot::append_milestone(Gcode *gcode, float target[], float rate_mm_s)
623 {
624 float deltas[3];
625 float unit_vec[3];
626 float actuator_pos[3];
627 float transformed_target[3]; // adjust target for bed compensation
628 float millimeters_of_travel;
629
630 // unity transform by default
631 memcpy(transformed_target, target, sizeof(transformed_target));
632
633 // check function pointer and call if set to transform the target to compensate for bed
634 if(compensationTransform) {
635 // some compensation strategies can transform XYZ, some just change Z
636 compensationTransform(transformed_target);
637 }
638
639 // find distance moved by each axis, use transformed target from last_transformed_target
640 for (int axis = X_AXIS; axis <= Z_AXIS; axis++){
641 deltas[axis] = transformed_target[axis] - transformed_last_milestone[axis];
642 }
643 // store last transformed
644 memcpy(this->transformed_last_milestone, transformed_target, sizeof(this->transformed_last_milestone));
645
646 // Compute how long this move moves, so we can attach it to the block for later use
647 millimeters_of_travel = sqrtf( powf( deltas[X_AXIS], 2 ) + powf( deltas[Y_AXIS], 2 ) + powf( deltas[Z_AXIS], 2 ) );
648
649 // find distance unit vector
650 for (int i = 0; i < 3; i++)
651 unit_vec[i] = deltas[i] / millimeters_of_travel;
652
653 // Do not move faster than the configured cartesian limits
654 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
655 if ( max_speeds[axis] > 0 ) {
656 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
657
658 if (axis_speed > max_speeds[axis])
659 rate_mm_s *= ( max_speeds[axis] / axis_speed );
660 }
661 }
662
663 // find actuator position given cartesian position, use actual adjusted target
664 arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
665
666 float isecs= rate_mm_s / millimeters_of_travel;
667 // check per-actuator speed limits
668 for (int actuator = 0; actuator <= 2; actuator++) {
669 float actuator_rate = fabsf(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * isecs;
670 if (actuator_rate > actuators[actuator]->get_max_rate()){
671 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
672 isecs= rate_mm_s / millimeters_of_travel;
673 }
674 }
675
676 // Append the block to the planner
677 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
678
679 // Update the last_milestone to the current target for the next time we use last_milestone, use the requested target not the adjusted one
680 memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->last_milestone[] = target[];
681
682 }
683
684 // Append a move to the queue ( cutting it into segments if needed )
685 void Robot::append_line(Gcode *gcode, float target[], float rate_mm_s )
686 {
687 // Find out the distance for this gcode
688 // NOTE we need to do sqrt here as this setting of millimeters_of_travel is used by extruder and other modules even if there is no XYZ move
689 gcode->millimeters_of_travel = sqrtf(powf( target[X_AXIS] - this->last_milestone[X_AXIS], 2 ) + powf( target[Y_AXIS] - this->last_milestone[Y_AXIS], 2 ) + powf( target[Z_AXIS] - this->last_milestone[Z_AXIS], 2 ));
690
691 // We ignore non- XYZ moves ( for example, extruder moves are not XYZ moves )
692 if( gcode->millimeters_of_travel < 0.00001F ) {
693 return;
694 }
695
696 // Mark the gcode as having a known distance
697 this->distance_in_gcode_is_known( gcode );
698
699 // if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit
700 // Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
701 // We also check we are not exceeding the E max_speed for the current extruder
702 // We ask Extruder to do all the work, but as Extruder won't even see this gcode until after it has been planned
703 // we need to ask it now passing in the relevant data.
704 // NOTE we need to do this before we segment the line (for deltas)
705 if(gcode->has_letter('E')) {
706 float data[2];
707 data[0]= gcode->get_value('E'); // E target (maybe absolute or relative)
708 data[1]= rate_mm_s / gcode->millimeters_of_travel; // inverted seconds for the move
709 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
710 rate_mm_s *= data[1];
711 //THEKERNEL->streams->printf("Extruder has changed the rate by %f to %f\n", data[1], rate_mm_s);
712 }
713 }
714
715 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
716 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
717 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
718 // The latter is more efficient and avoids splitting fast long lines into very small segments, like initial z move to 0, it is what Johanns Marlin delta port does
719 uint16_t segments;
720
721 if(this->delta_segments_per_second > 1.0F) {
722 // enabled if set to something > 1, it is set to 0.0 by default
723 // segment based on current speed and requested segments per second
724 // the faster the travel speed the fewer segments needed
725 // NOTE rate is mm/sec and we take into account any speed override
726 float seconds = gcode->millimeters_of_travel / rate_mm_s;
727 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
728 // TODO if we are only moving in Z on a delta we don't really need to segment at all
729
730 } else {
731 if(this->mm_per_line_segment == 0.0F) {
732 segments = 1; // don't split it up
733 } else {
734 segments = ceilf( gcode->millimeters_of_travel / this->mm_per_line_segment);
735 }
736 }
737
738 if (segments > 1) {
739 // A vector to keep track of the endpoint of each segment
740 float segment_delta[3];
741 float segment_end[3];
742
743 // How far do we move each segment?
744 for (int i = X_AXIS; i <= Z_AXIS; i++)
745 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
746
747 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
748 // We always add another point after this loop so we stop at segments-1, ie i < segments
749 for (int i = 1; i < segments; i++) {
750 if(halted) return; // don't queue any more segments
751 for(int axis = X_AXIS; axis <= Z_AXIS; axis++ )
752 segment_end[axis] = last_milestone[axis] + segment_delta[axis];
753
754 // Append the end of this segment to the queue
755 this->append_milestone(gcode, segment_end, rate_mm_s);
756 }
757 }
758
759 // Append the end of this full move to the queue
760 this->append_milestone(gcode, target, rate_mm_s);
761
762 // if adding these blocks didn't start executing, do that now
763 THEKERNEL->conveyor->ensure_running();
764 }
765
766
767 // Append an arc to the queue ( cutting it into segments as needed )
768 void Robot::append_arc(Gcode *gcode, float target[], float offset[], float radius, bool is_clockwise )
769 {
770
771 // Scary math
772 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
773 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
774 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
775 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
776 float r_axis1 = -offset[this->plane_axis_1];
777 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
778 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
779
780 // Patch from GRBL Firmware - Christoph Baumann 04072015
781 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
782 float angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
783 if (is_clockwise) { // Correct atan2 output per direction
784 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2*M_PI; }
785 } else {
786 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2*M_PI; }
787 }
788
789 // Find the distance for this gcode
790 gcode->millimeters_of_travel = hypotf(angular_travel * radius, fabs(linear_travel));
791
792 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
793 if( gcode->millimeters_of_travel < 0.00001F ) {
794 return;
795 }
796
797 // Mark the gcode as having a known distance
798 this->distance_in_gcode_is_known( gcode );
799
800 // Figure out how many segments for this gcode
801 uint16_t segments = floorf(gcode->millimeters_of_travel / this->mm_per_arc_segment);
802
803 float theta_per_segment = angular_travel / segments;
804 float linear_per_segment = linear_travel / segments;
805
806 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
807 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
808 r_T = [cos(phi) -sin(phi);
809 sin(phi) cos(phi] * r ;
810 For arc generation, the center of the circle is the axis of rotation and the radius vector is
811 defined from the circle center to the initial position. Each line segment is formed by successive
812 vector rotations. This requires only two cos() and sin() computations to form the rotation
813 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
814 all float numbers are single precision on the Arduino. (True float precision will not have
815 round off issues for CNC applications.) Single precision error can accumulate to be greater than
816 tool precision in some cases. Therefore, arc path correction is implemented.
817
818 Small angle approximation may be used to reduce computation overhead further. This approximation
819 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
820 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
821 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
822 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
823 issue for CNC machines with the single precision Arduino calculations.
824 This approximation also allows mc_arc to immediately insert a line segment into the planner
825 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
826 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
827 This is important when there are successive arc motions.
828 */
829 // Vector rotation matrix values
830 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
831 float sin_T = theta_per_segment;
832
833 float arc_target[3];
834 float sin_Ti;
835 float cos_Ti;
836 float r_axisi;
837 uint16_t i;
838 int8_t count = 0;
839
840 // Initialize the linear axis
841 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
842
843 for (i = 1; i < segments; i++) { // Increment (segments-1)
844 if(halted) return; // don't queue any more segments
845
846 if (count < this->arc_correction ) {
847 // Apply vector rotation matrix
848 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
849 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
850 r_axis1 = r_axisi;
851 count++;
852 } else {
853 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
854 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
855 cos_Ti = cosf(i * theta_per_segment);
856 sin_Ti = sinf(i * theta_per_segment);
857 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
858 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
859 count = 0;
860 }
861
862 // Update arc_target location
863 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
864 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
865 arc_target[this->plane_axis_2] += linear_per_segment;
866
867 // Append this segment to the queue
868 this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
869
870 }
871
872 // Ensure last segment arrives at target location.
873 this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute);
874 }
875
876 // Do the math for an arc and add it to the queue
877 void Robot::compute_arc(Gcode *gcode, float offset[], float target[])
878 {
879
880 // Find the radius
881 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
882
883 // Set clockwise/counter-clockwise sign for mc_arc computations
884 bool is_clockwise = false;
885 if( this->motion_mode == MOTION_MODE_CW_ARC ) {
886 is_clockwise = true;
887 }
888
889 // Append arc
890 this->append_arc(gcode, target, offset, radius, is_clockwise );
891
892 }
893
894
895 float Robot::theta(float x, float y)
896 {
897 float t = atanf(x / fabs(y));
898 if (y > 0) {
899 return(t);
900 } else {
901 if (t > 0) {
902 return(M_PI - t);
903 } else {
904 return(-M_PI - t);
905 }
906 }
907 }
908
909 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
910 {
911 this->plane_axis_0 = axis_0;
912 this->plane_axis_1 = axis_1;
913 this->plane_axis_2 = axis_2;
914 }
915
916 void Robot::clearToolOffset()
917 {
918 memset(this->toolOffset, 0, sizeof(this->toolOffset));
919 }
920
921 void Robot::setToolOffset(const float offset[3])
922 {
923 memcpy(this->toolOffset, offset, sizeof(this->toolOffset));
924 }
925