Merge branch 'switch' into edge
[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 <math.h>
12 #include <string>
13 using std::string;
14
15 #include "Planner.h"
16 #include "Conveyor.h"
17 #include "Robot.h"
18 #include "nuts_bolts.h"
19 #include "Pin.h"
20 #include "StepperMotor.h"
21 #include "Gcode.h"
22 #include "PublicDataRequest.h"
23 #include "arm_solutions/BaseSolution.h"
24 #include "arm_solutions/CartesianSolution.h"
25 #include "arm_solutions/RotatableCartesianSolution.h"
26 #include "arm_solutions/RostockSolution.h"
27 #include "arm_solutions/JohannKosselSolution.h"
28 #include "arm_solutions/HBotSolution.h"
29
30 #define default_seek_rate_checksum CHECKSUM("default_seek_rate")
31 #define default_feed_rate_checksum CHECKSUM("default_feed_rate")
32 #define mm_per_line_segment_checksum CHECKSUM("mm_per_line_segment")
33 #define delta_segments_per_second_checksum CHECKSUM("delta_segments_per_second")
34 #define mm_per_arc_segment_checksum CHECKSUM("mm_per_arc_segment")
35 #define arc_correction_checksum CHECKSUM("arc_correction")
36 #define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
37 #define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
38 #define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
39
40 // arm solutions
41 #define arm_solution_checksum CHECKSUM("arm_solution")
42 #define cartesian_checksum CHECKSUM("cartesian")
43 #define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
44 #define rostock_checksum CHECKSUM("rostock")
45 #define delta_checksum CHECKSUM("delta")
46 #define hbot_checksum CHECKSUM("hbot")
47 #define corexy_checksum CHECKSUM("corexy")
48 #define kossel_checksum CHECKSUM("kossel")
49
50 // stepper motor stuff
51 #define alpha_step_pin_checksum CHECKSUM("alpha_step_pin")
52 #define beta_step_pin_checksum CHECKSUM("beta_step_pin")
53 #define gamma_step_pin_checksum CHECKSUM("gamma_step_pin")
54 #define alpha_dir_pin_checksum CHECKSUM("alpha_dir_pin")
55 #define beta_dir_pin_checksum CHECKSUM("beta_dir_pin")
56 #define gamma_dir_pin_checksum CHECKSUM("gamma_dir_pin")
57 #define alpha_en_pin_checksum CHECKSUM("alpha_en_pin")
58 #define beta_en_pin_checksum CHECKSUM("beta_en_pin")
59 #define gamma_en_pin_checksum CHECKSUM("gamma_en_pin")
60
61 #define alpha_steps_per_mm_checksum CHECKSUM("alpha_steps_per_mm")
62 #define beta_steps_per_mm_checksum CHECKSUM("beta_steps_per_mm")
63 #define gamma_steps_per_mm_checksum CHECKSUM("gamma_steps_per_mm")
64
65 #define alpha_max_rate_checksum CHECKSUM("alpha_max_rate")
66 #define beta_max_rate_checksum CHECKSUM("beta_max_rate")
67 #define gamma_max_rate_checksum CHECKSUM("gamma_max_rate")
68
69
70 // new-style actuator stuff
71 #define actuator_checksum CHEKCSUM("actuator")
72
73 #define step_pin_checksum CHECKSUM("step_pin")
74 #define dir_pin_checksum CHEKCSUM("dir_pin")
75 #define en_pin_checksum CHECKSUM("en_pin")
76
77 #define steps_per_mm_checksum CHECKSUM("steps_per_mm")
78 #define max_rate_checksum CHECKSUM("max_rate")
79
80 #define alpha_checksum CHECKSUM("alpha")
81 #define beta_checksum CHECKSUM("beta")
82 #define gamma_checksum CHECKSUM("gamma")
83
84
85 // 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
86 // It takes care of cutting arcs into segments, same thing for line that are too long
87 #define max(a,b) (((a) > (b)) ? (a) : (b))
88
89 Robot::Robot(){
90 this->inch_mode = false;
91 this->absolute_mode = true;
92 this->motion_mode = MOTION_MODE_SEEK;
93 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
94 clear_vector(this->last_milestone);
95 this->arm_solution = NULL;
96 seconds_per_minute = 60.0F;
97 }
98
99 //Called when the module has just been loaded
100 void Robot::on_module_loaded() {
101 register_for_event(ON_CONFIG_RELOAD);
102 this->register_for_event(ON_GCODE_RECEIVED);
103 this->register_for_event(ON_GET_PUBLIC_DATA);
104 this->register_for_event(ON_SET_PUBLIC_DATA);
105
106 // Configuration
107 this->on_config_reload(this);
108 }
109
110 void Robot::on_config_reload(void* argument){
111
112 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
113 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
114 // To make adding those solution easier, they have their own, separate object.
115 // Here we read the config to find out which arm solution to use
116 if (this->arm_solution) delete this->arm_solution;
117 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
118 // Note checksums are not const expressions when in debug mode, so don't use switch
119 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
120 this->arm_solution = new HBotSolution(THEKERNEL->config);
121
122 }else if(solution_checksum == rostock_checksum) {
123 this->arm_solution = new RostockSolution(THEKERNEL->config);
124
125 }else if(solution_checksum == kossel_checksum) {
126 this->arm_solution = new JohannKosselSolution(THEKERNEL->config);
127
128 }else if(solution_checksum == delta_checksum) {
129 // place holder for now
130 this->arm_solution = new RostockSolution(THEKERNEL->config);
131
132 }else if(solution_checksum == rotatable_cartesian_checksum) {
133 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
134
135 }else if(solution_checksum == cartesian_checksum) {
136 this->arm_solution = new CartesianSolution(THEKERNEL->config);
137
138 }else{
139 this->arm_solution = new CartesianSolution(THEKERNEL->config);
140 }
141
142
143 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
144 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
145 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
146 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
147 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.5f)->as_number();
148 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
149
150 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
151 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
152 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
153
154 Pin alpha_step_pin;
155 Pin alpha_dir_pin;
156 Pin alpha_en_pin;
157 Pin beta_step_pin;
158 Pin beta_dir_pin;
159 Pin beta_en_pin;
160 Pin gamma_step_pin;
161 Pin gamma_dir_pin;
162 Pin gamma_en_pin;
163
164 alpha_step_pin.from_string( THEKERNEL->config->value(alpha_step_pin_checksum )->by_default("2.0" )->as_string())->as_output();
165 alpha_dir_pin.from_string( THEKERNEL->config->value(alpha_dir_pin_checksum )->by_default("0.5" )->as_string())->as_output();
166 alpha_en_pin.from_string( THEKERNEL->config->value(alpha_en_pin_checksum )->by_default("0.4" )->as_string())->as_output();
167 beta_step_pin.from_string( THEKERNEL->config->value(beta_step_pin_checksum )->by_default("2.1" )->as_string())->as_output();
168 beta_dir_pin.from_string( THEKERNEL->config->value(beta_dir_pin_checksum )->by_default("0.11" )->as_string())->as_output();
169 beta_en_pin.from_string( THEKERNEL->config->value(beta_en_pin_checksum )->by_default("0.10" )->as_string())->as_output();
170 gamma_step_pin.from_string( THEKERNEL->config->value(gamma_step_pin_checksum )->by_default("2.2" )->as_string())->as_output();
171 gamma_dir_pin.from_string( THEKERNEL->config->value(gamma_dir_pin_checksum )->by_default("0.20" )->as_string())->as_output();
172 gamma_en_pin.from_string( THEKERNEL->config->value(gamma_en_pin_checksum )->by_default("0.19" )->as_string())->as_output();
173
174 float steps_per_mm[3] = {
175 THEKERNEL->config->value(alpha_steps_per_mm_checksum)->by_default( 80.0F)->as_number(),
176 THEKERNEL->config->value(beta_steps_per_mm_checksum )->by_default( 80.0F)->as_number(),
177 THEKERNEL->config->value(gamma_steps_per_mm_checksum)->by_default(2560.0F)->as_number(),
178 };
179
180 // TODO: delete or detect old steppermotors
181 // Make our 3 StepperMotors
182 this->alpha_stepper_motor = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(alpha_step_pin, alpha_dir_pin, alpha_en_pin) );
183 this->beta_stepper_motor = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(beta_step_pin, beta_dir_pin, beta_en_pin ) );
184 this->gamma_stepper_motor = THEKERNEL->step_ticker->add_stepper_motor( new StepperMotor(gamma_step_pin, gamma_dir_pin, gamma_en_pin) );
185
186 alpha_stepper_motor->change_steps_per_mm(steps_per_mm[0]);
187 beta_stepper_motor->change_steps_per_mm(steps_per_mm[1]);
188 gamma_stepper_motor->change_steps_per_mm(steps_per_mm[2]);
189
190 alpha_stepper_motor->max_rate = THEKERNEL->config->value(alpha_max_rate_checksum)->by_default(30000.0F)->as_number() / 60.0F;
191 beta_stepper_motor->max_rate = THEKERNEL->config->value(beta_max_rate_checksum )->by_default(30000.0F)->as_number() / 60.0F;
192 gamma_stepper_motor->max_rate = THEKERNEL->config->value(gamma_max_rate_checksum)->by_default(30000.0F)->as_number() / 60.0F;
193
194 actuators.clear();
195 actuators.push_back(alpha_stepper_motor);
196 actuators.push_back(beta_stepper_motor);
197 actuators.push_back(gamma_stepper_motor);
198 }
199
200 void Robot::on_get_public_data(void* argument){
201 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
202
203 if(!pdr->starts_with(robot_checksum)) return;
204
205 if(pdr->second_element_is(speed_override_percent_checksum)) {
206 static float return_data;
207 return_data = 100.0F * 60.0F / seconds_per_minute;
208 pdr->set_data_ptr(&return_data);
209 pdr->set_taken();
210
211 }else if(pdr->second_element_is(current_position_checksum)) {
212 static float return_data[3];
213 return_data[0]= from_millimeters(this->last_milestone[0]);
214 return_data[1]= from_millimeters(this->last_milestone[1]);
215 return_data[2]= from_millimeters(this->last_milestone[2]);
216
217 pdr->set_data_ptr(&return_data);
218 pdr->set_taken();
219 }
220 }
221
222 void Robot::on_set_public_data(void* argument){
223 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
224
225 if(!pdr->starts_with(robot_checksum)) return;
226
227 if(pdr->second_element_is(speed_override_percent_checksum)) {
228 // NOTE do not use this while printing!
229 float t= *static_cast<float*>(pdr->get_data_ptr());
230 // enforce minimum 10% speed
231 if (t < 10.0F) t= 10.0F;
232
233 this->seconds_per_minute = t / 0.6F; // t * 60 / 100
234 pdr->set_taken();
235 }
236 }
237
238 //A GCode has been received
239 //See if the current Gcode line has some orders for us
240 void Robot::on_gcode_received(void * argument){
241 Gcode* gcode = static_cast<Gcode*>(argument);
242
243 //Temp variables, constant properties are stored in the object
244 uint8_t next_action = NEXT_ACTION_DEFAULT;
245 this->motion_mode = -1;
246
247 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
248 if( gcode->has_g){
249 switch( gcode->g ){
250 case 0: this->motion_mode = MOTION_MODE_SEEK; gcode->mark_as_taken(); break;
251 case 1: this->motion_mode = MOTION_MODE_LINEAR; gcode->mark_as_taken(); break;
252 case 2: this->motion_mode = MOTION_MODE_CW_ARC; gcode->mark_as_taken(); break;
253 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; gcode->mark_as_taken(); break;
254 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); gcode->mark_as_taken(); break;
255 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); gcode->mark_as_taken(); break;
256 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); gcode->mark_as_taken(); break;
257 case 20: this->inch_mode = true; gcode->mark_as_taken(); break;
258 case 21: this->inch_mode = false; gcode->mark_as_taken(); break;
259 case 90: this->absolute_mode = true; gcode->mark_as_taken(); break;
260 case 91: this->absolute_mode = false; gcode->mark_as_taken(); break;
261 case 92: {
262 if(gcode->get_num_args() == 0){
263 clear_vector(this->last_milestone);
264 }else{
265 for (char letter = 'X'; letter <= 'Z'; letter++){
266 if ( gcode->has_letter(letter) )
267 this->last_milestone[letter-'X'] = this->to_millimeters(gcode->get_value(letter));
268 }
269 }
270
271 // TODO: handle any number of actuators
272 float actuator_pos[3];
273 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
274
275 for (int i = 0; i < 3; i++)
276 actuators[i]->change_last_milestone(actuator_pos[i]);
277
278 gcode->mark_as_taken();
279 return;
280 }
281 }
282 }else if( gcode->has_m){
283 switch( gcode->m ){
284 case 92: // M92 - set steps per mm
285 if (gcode->has_letter('X'))
286 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
287 if (gcode->has_letter('Y'))
288 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
289 if (gcode->has_letter('Z'))
290 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
291 if (gcode->has_letter('F'))
292 seconds_per_minute = gcode->get_value('F');
293
294 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);
295 gcode->add_nl = true;
296 gcode->mark_as_taken();
297 return;
298 case 114: gcode->stream->printf("C: X:%1.3f Y:%1.3f Z:%1.3f ",
299 from_millimeters(this->last_milestone[0]),
300 from_millimeters(this->last_milestone[1]),
301 from_millimeters(this->last_milestone[2]));
302 gcode->add_nl = true;
303 gcode->mark_as_taken();
304 return;
305
306 case 203: // M203 Set maximum feedrates in mm/sec
307 if (gcode->has_letter('X'))
308 this->max_speeds[X_AXIS]= gcode->get_value('X');
309 if (gcode->has_letter('Y'))
310 this->max_speeds[Y_AXIS]= gcode->get_value('Y');
311 if (gcode->has_letter('Z'))
312 this->max_speeds[Z_AXIS]= gcode->get_value('Z');
313 if (gcode->has_letter('A'))
314 alpha_stepper_motor->max_rate= gcode->get_value('A');
315 if (gcode->has_letter('B'))
316 beta_stepper_motor->max_rate= gcode->get_value('B');
317 if (gcode->has_letter('C'))
318 gamma_stepper_motor->max_rate= gcode->get_value('C');
319
320 gcode->stream->printf("X:%g Y:%g Z:%g A:%g B:%g C:%g ",
321 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS],
322 alpha_stepper_motor->max_rate, beta_stepper_motor->max_rate, gamma_stepper_motor->max_rate);
323 gcode->add_nl = true;
324 gcode->mark_as_taken();
325 break;
326
327 case 204: // M204 Snnn - set acceleration to nnn, NB only Snnn is currently supported
328 gcode->mark_as_taken();
329
330 if (gcode->has_letter('S'))
331 {
332 // TODO for safety so it applies only to following gcodes, maybe a better way to do this?
333 THEKERNEL->conveyor->wait_for_empty_queue();
334 float acc= gcode->get_value('S'); // mm/s^2
335 // enforce minimum
336 if (acc < 1.0F)
337 acc = 1.0F;
338 THEKERNEL->planner->acceleration= acc;
339 }
340 break;
341
342 case 205: // M205 Xnnn - set junction deviation Snnn - Set minimum planner speed
343 gcode->mark_as_taken();
344 if (gcode->has_letter('X'))
345 {
346 float jd= gcode->get_value('X');
347 // enforce minimum
348 if (jd < 0.0F)
349 jd = 0.0F;
350 THEKERNEL->planner->junction_deviation= jd;
351 }
352 if (gcode->has_letter('S'))
353 {
354 float mps= gcode->get_value('S');
355 // enforce minimum
356 if (mps < 0.0F)
357 mps = 0.0F;
358 THEKERNEL->planner->minimum_planner_speed= mps;
359 }
360 break;
361
362 case 220: // M220 - speed override percentage
363 gcode->mark_as_taken();
364 if (gcode->has_letter('S'))
365 {
366 float factor = gcode->get_value('S');
367 // enforce minimum 10% speed
368 if (factor < 10.0F)
369 factor = 10.0F;
370 // enforce maximum 10x speed
371 if (factor > 1000.0F)
372 factor = 1000.0F;
373
374 seconds_per_minute = 6000.0F / factor;
375 }
376 break;
377
378 case 400: // wait until all moves are done up to this point
379 gcode->mark_as_taken();
380 THEKERNEL->conveyor->wait_for_empty_queue();
381 break;
382
383 case 500: // M500 saves some volatile settings to config override file
384 case 503: // M503 just prints the settings
385 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);
386 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f\n", THEKERNEL->planner->acceleration);
387 gcode->stream->printf(";X- Junction Deviation, S - Minimum Planner speed:\nM205 X%1.5f S%1.5f\n", THEKERNEL->planner->junction_deviation, THEKERNEL->planner->minimum_planner_speed);
388 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",
389 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS],
390 alpha_stepper_motor->max_rate, beta_stepper_motor->max_rate, gamma_stepper_motor->max_rate);
391 gcode->mark_as_taken();
392 break;
393
394 case 665: // M665 set optional arm solution variables based on arm solution
395 gcode->mark_as_taken();
396 // the parameter args could be any letter so try each one
397 for(char c='A';c<='Z';c++) {
398 float v;
399 bool supported= arm_solution->get_optional(c, &v); // retrieve current value if supported
400
401 if(supported && gcode->has_letter(c)) { // set new value if supported
402 v= gcode->get_value(c);
403 arm_solution->set_optional(c, v);
404 }
405 if(supported) { // print all current values of supported options
406 gcode->stream->printf("%c %8.3f ", c, v);
407 gcode->add_nl = true;
408 }
409 }
410 break;
411
412 }
413 }
414
415 if( this->motion_mode < 0)
416 return;
417
418 //Get parameters
419 float target[3], offset[3];
420 clear_vector(offset);
421
422 memcpy(target, this->last_milestone, sizeof(target)); //default to last target
423
424 for(char letter = 'I'; letter <= 'K'; letter++){
425 if( gcode->has_letter(letter) ){
426 offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter));
427 }
428 }
429 for(char letter = 'X'; letter <= 'Z'; letter++){
430 if( gcode->has_letter(letter) ){
431 target[letter-'X'] = this->to_millimeters(gcode->get_value(letter)) + ( this->absolute_mode ? 0 : target[letter-'X']);
432 }
433 }
434
435 if( gcode->has_letter('F') )
436 {
437 if( this->motion_mode == MOTION_MODE_SEEK )
438 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
439 else
440 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
441 }
442
443 //Perform any physical actions
444 switch( next_action ){
445 case NEXT_ACTION_DEFAULT:
446 switch(this->motion_mode){
447 case MOTION_MODE_CANCEL: break;
448 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate / seconds_per_minute ); break;
449 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate / seconds_per_minute ); break;
450 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
451 }
452 break;
453 }
454
455 // As far as the parser is concerned, the position is now == target. In reality the
456 // motion control system might still be processing the action and the real tool position
457 // in any intermediate location.
458 memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->position[] = target[];
459
460 }
461
462 // We received a new gcode, and one of the functions
463 // determined the distance for that given gcode. So now we can attach this gcode to the right block
464 // and continue
465 void Robot::distance_in_gcode_is_known(Gcode* gcode){
466
467 //If the queue is empty, execute immediatly, otherwise attach to the last added block
468 THEKERNEL->conveyor->append_gcode(gcode);
469 }
470
471 // Reset the position for all axes ( used in homing and G92 stuff )
472 void Robot::reset_axis_position(float position, int axis) {
473 this->last_milestone[axis] = position;
474
475 float actuator_pos[3];
476 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
477
478 for (int i = 0; i < 3; i++)
479 actuators[i]->change_last_milestone(actuator_pos[i]);
480 }
481
482
483 // Convert target from millimeters to steps, and append this to the planner
484 void Robot::append_milestone( float target[], float rate_mm_s )
485 {
486 float deltas[3];
487 float unit_vec[3];
488 float actuator_pos[3];
489 float millimeters_of_travel;
490
491 // find distance moved by each axis
492 for (int axis = X_AXIS; axis <= Z_AXIS; axis++)
493 deltas[axis] = target[axis] - last_milestone[axis];
494
495 // Compute how long this move moves, so we can attach it to the block for later use
496 millimeters_of_travel = sqrtf( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) );
497
498 // find distance unit vector
499 for (int i = 0; i < 3; i++)
500 unit_vec[i] = deltas[i] / millimeters_of_travel;
501
502 // Do not move faster than the configured cartesian limits
503 for (int axis = X_AXIS; axis <= Z_AXIS; axis++)
504 {
505 if ( max_speeds[axis] > 0 )
506 {
507 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
508
509 if (axis_speed > max_speeds[axis])
510 rate_mm_s *= ( max_speeds[axis] / axis_speed );
511 }
512 }
513
514 // find actuator position given cartesian position
515 arm_solution->cartesian_to_actuator( target, actuator_pos );
516
517 // check per-actuator speed limits
518 for (int actuator = 0; actuator <= 2; actuator++)
519 {
520 float actuator_rate = fabs(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * rate_mm_s / millimeters_of_travel;
521
522 if (actuator_rate > actuators[actuator]->max_rate)
523 rate_mm_s *= (actuators[actuator]->max_rate / actuator_rate);
524 }
525
526 // Append the block to the planner
527 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
528
529 // Update the last_milestone to the current target for the next time we use last_milestone
530 memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->last_milestone[] = target[];
531
532 }
533
534 // Append a move to the queue ( cutting it into segments if needed )
535 void Robot::append_line(Gcode* gcode, float target[], float rate_mm_s ){
536
537 // Find out the distance for this gcode
538 gcode->millimeters_of_travel = pow( target[X_AXIS]-this->last_milestone[X_AXIS], 2 ) + pow( target[Y_AXIS]-this->last_milestone[Y_AXIS], 2 ) + pow( target[Z_AXIS]-this->last_milestone[Z_AXIS], 2 );
539
540 // We ignore non-moves ( for example, extruder moves are not XYZ moves )
541 if( gcode->millimeters_of_travel < 1e-8F ){
542 return;
543 }
544
545 gcode->millimeters_of_travel = sqrtf(gcode->millimeters_of_travel);
546
547 // Mark the gcode as having a known distance
548 this->distance_in_gcode_is_known( gcode );
549
550 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
551 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
552 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second 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
553 uint16_t segments;
554
555 if(this->delta_segments_per_second > 1.0F) {
556 // enabled if set to something > 1, it is set to 0.0 by default
557 // segment based on current speed and requested segments per second
558 // the faster the travel speed the fewer segments needed
559 // NOTE rate is mm/sec and we take into account any speed override
560 float seconds = gcode->millimeters_of_travel / rate_mm_s;
561 segments= max(1, ceil(this->delta_segments_per_second * seconds));
562 // TODO if we are only moving in Z on a delta we don't really need to segment at all
563
564 }else{
565 if(this->mm_per_line_segment == 0.0F){
566 segments= 1; // don't split it up
567 }else{
568 segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
569 }
570 }
571
572 if (segments > 1)
573 {
574 // A vector to keep track of the endpoint of each segment
575 float segment_delta[3];
576 float segment_end[3];
577
578 // How far do we move each segment?
579 for (int i = X_AXIS; i <= Z_AXIS; i++)
580 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
581
582 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
583 // We always add another point after this loop so we stop at segments-1, ie i < segments
584 for (int i = 1; i < segments; i++)
585 {
586 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ )
587 segment_end[axis] = last_milestone[axis] + segment_delta[axis];
588
589 // Append the end of this segment to the queue
590 this->append_milestone(segment_end, rate_mm_s);
591 }
592 }
593
594 // Append the end of this full move to the queue
595 this->append_milestone(target, rate_mm_s);
596
597 // if adding these blocks didn't start executing, do that now
598 THEKERNEL->conveyor->ensure_running();
599 }
600
601
602 // Append an arc to the queue ( cutting it into segments as needed )
603 void Robot::append_arc(Gcode* gcode, float target[], float offset[], float radius, bool is_clockwise ){
604
605 // Scary math
606 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
607 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
608 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
609 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
610 float r_axis1 = -offset[this->plane_axis_1];
611 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
612 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
613
614 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
615 float angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
616 if (angular_travel < 0) { angular_travel += 2*M_PI; }
617 if (is_clockwise) { angular_travel -= 2*M_PI; }
618
619 // Find the distance for this gcode
620 gcode->millimeters_of_travel = hypotf(angular_travel*radius, fabs(linear_travel));
621
622 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
623 if( gcode->millimeters_of_travel < 0.0001F ){ return; }
624
625 // Mark the gcode as having a known distance
626 this->distance_in_gcode_is_known( gcode );
627
628 // Figure out how many segments for this gcode
629 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment);
630
631 float theta_per_segment = angular_travel/segments;
632 float linear_per_segment = linear_travel/segments;
633
634 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
635 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
636 r_T = [cos(phi) -sin(phi);
637 sin(phi) cos(phi] * r ;
638 For arc generation, the center of the circle is the axis of rotation and the radius vector is
639 defined from the circle center to the initial position. Each line segment is formed by successive
640 vector rotations. This requires only two cos() and sin() computations to form the rotation
641 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
642 all float numbers are single precision on the Arduino. (True float precision will not have
643 round off issues for CNC applications.) Single precision error can accumulate to be greater than
644 tool precision in some cases. Therefore, arc path correction is implemented.
645
646 Small angle approximation may be used to reduce computation overhead further. This approximation
647 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
648 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
649 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
650 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
651 issue for CNC machines with the single precision Arduino calculations.
652 This approximation also allows mc_arc to immediately insert a line segment into the planner
653 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
654 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
655 This is important when there are successive arc motions.
656 */
657 // Vector rotation matrix values
658 float cos_T = 1-0.5F*theta_per_segment*theta_per_segment; // Small angle approximation
659 float sin_T = theta_per_segment;
660
661 float arc_target[3];
662 float sin_Ti;
663 float cos_Ti;
664 float r_axisi;
665 uint16_t i;
666 int8_t count = 0;
667
668 // Initialize the linear axis
669 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
670
671 for (i = 1; i<segments; i++) { // Increment (segments-1)
672
673 if (count < this->arc_correction ) {
674 // Apply vector rotation matrix
675 r_axisi = r_axis0*sin_T + r_axis1*cos_T;
676 r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
677 r_axis1 = r_axisi;
678 count++;
679 } else {
680 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
681 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
682 cos_Ti = cosf(i*theta_per_segment);
683 sin_Ti = sinf(i*theta_per_segment);
684 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
685 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
686 count = 0;
687 }
688
689 // Update arc_target location
690 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
691 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
692 arc_target[this->plane_axis_2] += linear_per_segment;
693
694 // Append this segment to the queue
695 this->append_milestone(arc_target, this->feed_rate / seconds_per_minute);
696
697 }
698
699 // Ensure last segment arrives at target location.
700 this->append_milestone(target, this->feed_rate / seconds_per_minute);
701 }
702
703 // Do the math for an arc and add it to the queue
704 void Robot::compute_arc(Gcode* gcode, float offset[], float target[]){
705
706 // Find the radius
707 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
708
709 // Set clockwise/counter-clockwise sign for mc_arc computations
710 bool is_clockwise = false;
711 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; }
712
713 // Append arc
714 this->append_arc(gcode, target, offset, radius, is_clockwise );
715
716 }
717
718
719 float Robot::theta(float x, float y){
720 float t = atanf(x/fabs(y));
721 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}}
722 }
723
724 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){
725 this->plane_axis_0 = axis_0;
726 this->plane_axis_1 = axis_1;
727 this->plane_axis_2 = axis_2;
728 }
729
730