Initialise actuator positions to current cartesian position (X0 Y0 Z0) so the first...
[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 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
200 // so the first move can be correct if homing is not performed
201 float actuator_pos[3];
202 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
203 for (int i = 0; i < 3; i++)
204 actuators[i]->change_last_milestone(actuator_pos[i]);
205 }
206
207 void Robot::on_get_public_data(void* argument){
208 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
209
210 if(!pdr->starts_with(robot_checksum)) return;
211
212 if(pdr->second_element_is(speed_override_percent_checksum)) {
213 static float return_data;
214 return_data = 100.0F * 60.0F / seconds_per_minute;
215 pdr->set_data_ptr(&return_data);
216 pdr->set_taken();
217
218 }else if(pdr->second_element_is(current_position_checksum)) {
219 static float return_data[3];
220 return_data[0]= from_millimeters(this->last_milestone[0]);
221 return_data[1]= from_millimeters(this->last_milestone[1]);
222 return_data[2]= from_millimeters(this->last_milestone[2]);
223
224 pdr->set_data_ptr(&return_data);
225 pdr->set_taken();
226 }
227 }
228
229 void Robot::on_set_public_data(void* argument){
230 PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
231
232 if(!pdr->starts_with(robot_checksum)) return;
233
234 if(pdr->second_element_is(speed_override_percent_checksum)) {
235 // NOTE do not use this while printing!
236 float t= *static_cast<float*>(pdr->get_data_ptr());
237 // enforce minimum 10% speed
238 if (t < 10.0F) t= 10.0F;
239
240 this->seconds_per_minute = t / 0.6F; // t * 60 / 100
241 pdr->set_taken();
242 }
243 }
244
245 //A GCode has been received
246 //See if the current Gcode line has some orders for us
247 void Robot::on_gcode_received(void * argument){
248 Gcode* gcode = static_cast<Gcode*>(argument);
249
250 //Temp variables, constant properties are stored in the object
251 uint8_t next_action = NEXT_ACTION_DEFAULT;
252 this->motion_mode = -1;
253
254 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
255 if( gcode->has_g){
256 switch( gcode->g ){
257 case 0: this->motion_mode = MOTION_MODE_SEEK; gcode->mark_as_taken(); break;
258 case 1: this->motion_mode = MOTION_MODE_LINEAR; gcode->mark_as_taken(); break;
259 case 2: this->motion_mode = MOTION_MODE_CW_ARC; gcode->mark_as_taken(); break;
260 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; gcode->mark_as_taken(); break;
261 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); gcode->mark_as_taken(); break;
262 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); gcode->mark_as_taken(); break;
263 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); gcode->mark_as_taken(); break;
264 case 20: this->inch_mode = true; gcode->mark_as_taken(); break;
265 case 21: this->inch_mode = false; gcode->mark_as_taken(); break;
266 case 90: this->absolute_mode = true; gcode->mark_as_taken(); break;
267 case 91: this->absolute_mode = false; gcode->mark_as_taken(); break;
268 case 92: {
269 if(gcode->get_num_args() == 0){
270 clear_vector(this->last_milestone);
271 }else{
272 for (char letter = 'X'; letter <= 'Z'; letter++){
273 if ( gcode->has_letter(letter) )
274 this->last_milestone[letter-'X'] = this->to_millimeters(gcode->get_value(letter));
275 }
276 }
277
278 // TODO: handle any number of actuators
279 float actuator_pos[3];
280 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
281
282 for (int i = 0; i < 3; i++)
283 actuators[i]->change_last_milestone(actuator_pos[i]);
284
285 gcode->mark_as_taken();
286 return;
287 }
288 }
289 }else if( gcode->has_m){
290 switch( gcode->m ){
291 case 92: // M92 - set steps per mm
292 if (gcode->has_letter('X'))
293 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
294 if (gcode->has_letter('Y'))
295 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
296 if (gcode->has_letter('Z'))
297 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
298 if (gcode->has_letter('F'))
299 seconds_per_minute = gcode->get_value('F');
300
301 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);
302 gcode->add_nl = true;
303 gcode->mark_as_taken();
304 return;
305 case 114: gcode->stream->printf("C: X:%1.3f Y:%1.3f Z:%1.3f ",
306 from_millimeters(this->last_milestone[0]),
307 from_millimeters(this->last_milestone[1]),
308 from_millimeters(this->last_milestone[2]));
309 gcode->add_nl = true;
310 gcode->mark_as_taken();
311 return;
312
313 case 203: // M203 Set maximum feedrates in mm/sec
314 if (gcode->has_letter('X'))
315 this->max_speeds[X_AXIS]= gcode->get_value('X');
316 if (gcode->has_letter('Y'))
317 this->max_speeds[Y_AXIS]= gcode->get_value('Y');
318 if (gcode->has_letter('Z'))
319 this->max_speeds[Z_AXIS]= gcode->get_value('Z');
320 if (gcode->has_letter('A'))
321 alpha_stepper_motor->max_rate= gcode->get_value('A');
322 if (gcode->has_letter('B'))
323 beta_stepper_motor->max_rate= gcode->get_value('B');
324 if (gcode->has_letter('C'))
325 gamma_stepper_motor->max_rate= gcode->get_value('C');
326
327 gcode->stream->printf("X:%g Y:%g Z:%g A:%g B:%g C:%g ",
328 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS],
329 alpha_stepper_motor->max_rate, beta_stepper_motor->max_rate, gamma_stepper_motor->max_rate);
330 gcode->add_nl = true;
331 gcode->mark_as_taken();
332 break;
333
334 case 204: // M204 Snnn - set acceleration to nnn, NB only Snnn is currently supported
335 gcode->mark_as_taken();
336
337 if (gcode->has_letter('S'))
338 {
339 // TODO for safety so it applies only to following gcodes, maybe a better way to do this?
340 THEKERNEL->conveyor->wait_for_empty_queue();
341 float acc= gcode->get_value('S'); // mm/s^2
342 // enforce minimum
343 if (acc < 1.0F)
344 acc = 1.0F;
345 THEKERNEL->planner->acceleration= acc;
346 }
347 break;
348
349 case 205: // M205 Xnnn - set junction deviation Snnn - Set minimum planner speed
350 gcode->mark_as_taken();
351 if (gcode->has_letter('X'))
352 {
353 float jd= gcode->get_value('X');
354 // enforce minimum
355 if (jd < 0.0F)
356 jd = 0.0F;
357 THEKERNEL->planner->junction_deviation= jd;
358 }
359 if (gcode->has_letter('S'))
360 {
361 float mps= gcode->get_value('S');
362 // enforce minimum
363 if (mps < 0.0F)
364 mps = 0.0F;
365 THEKERNEL->planner->minimum_planner_speed= mps;
366 }
367 break;
368
369 case 220: // M220 - speed override percentage
370 gcode->mark_as_taken();
371 if (gcode->has_letter('S'))
372 {
373 float factor = gcode->get_value('S');
374 // enforce minimum 10% speed
375 if (factor < 10.0F)
376 factor = 10.0F;
377 // enforce maximum 10x speed
378 if (factor > 1000.0F)
379 factor = 1000.0F;
380
381 seconds_per_minute = 6000.0F / factor;
382 }
383 break;
384
385 case 400: // wait until all moves are done up to this point
386 gcode->mark_as_taken();
387 THEKERNEL->conveyor->wait_for_empty_queue();
388 break;
389
390 case 500: // M500 saves some volatile settings to config override file
391 case 503: // M503 just prints the settings
392 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);
393 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f\n", THEKERNEL->planner->acceleration);
394 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);
395 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",
396 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS],
397 alpha_stepper_motor->max_rate, beta_stepper_motor->max_rate, gamma_stepper_motor->max_rate);
398 gcode->mark_as_taken();
399 break;
400
401 case 665: // M665 set optional arm solution variables based on arm solution
402 gcode->mark_as_taken();
403 // the parameter args could be any letter so try each one
404 for(char c='A';c<='Z';c++) {
405 float v;
406 bool supported= arm_solution->get_optional(c, &v); // retrieve current value if supported
407
408 if(supported && gcode->has_letter(c)) { // set new value if supported
409 v= gcode->get_value(c);
410 arm_solution->set_optional(c, v);
411 }
412 if(supported) { // print all current values of supported options
413 gcode->stream->printf("%c %8.3f ", c, v);
414 gcode->add_nl = true;
415 }
416 }
417 break;
418
419 }
420 }
421
422 if( this->motion_mode < 0)
423 return;
424
425 //Get parameters
426 float target[3], offset[3];
427 clear_vector(offset);
428
429 memcpy(target, this->last_milestone, sizeof(target)); //default to last target
430
431 for(char letter = 'I'; letter <= 'K'; letter++){
432 if( gcode->has_letter(letter) ){
433 offset[letter-'I'] = this->to_millimeters(gcode->get_value(letter));
434 }
435 }
436 for(char letter = 'X'; letter <= 'Z'; letter++){
437 if( gcode->has_letter(letter) ){
438 target[letter-'X'] = this->to_millimeters(gcode->get_value(letter)) + ( this->absolute_mode ? 0 : target[letter-'X']);
439 }
440 }
441
442 if( gcode->has_letter('F') )
443 {
444 if( this->motion_mode == MOTION_MODE_SEEK )
445 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
446 else
447 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
448 }
449
450 //Perform any physical actions
451 switch( next_action ){
452 case NEXT_ACTION_DEFAULT:
453 switch(this->motion_mode){
454 case MOTION_MODE_CANCEL: break;
455 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate / seconds_per_minute ); break;
456 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate / seconds_per_minute ); break;
457 case MOTION_MODE_CW_ARC: case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
458 }
459 break;
460 }
461
462 // As far as the parser is concerned, the position is now == target. In reality the
463 // motion control system might still be processing the action and the real tool position
464 // in any intermediate location.
465 memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->position[] = target[];
466
467 }
468
469 // We received a new gcode, and one of the functions
470 // determined the distance for that given gcode. So now we can attach this gcode to the right block
471 // and continue
472 void Robot::distance_in_gcode_is_known(Gcode* gcode){
473
474 //If the queue is empty, execute immediatly, otherwise attach to the last added block
475 THEKERNEL->conveyor->append_gcode(gcode);
476 }
477
478 // Reset the position for all axes ( used in homing and G92 stuff )
479 void Robot::reset_axis_position(float position, int axis) {
480 this->last_milestone[axis] = position;
481
482 float actuator_pos[3];
483 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
484
485 for (int i = 0; i < 3; i++)
486 actuators[i]->change_last_milestone(actuator_pos[i]);
487 }
488
489
490 // Convert target from millimeters to steps, and append this to the planner
491 void Robot::append_milestone( float target[], float rate_mm_s )
492 {
493 float deltas[3];
494 float unit_vec[3];
495 float actuator_pos[3];
496 float millimeters_of_travel;
497
498 // find distance moved by each axis
499 for (int axis = X_AXIS; axis <= Z_AXIS; axis++)
500 deltas[axis] = target[axis] - last_milestone[axis];
501
502 // Compute how long this move moves, so we can attach it to the block for later use
503 millimeters_of_travel = sqrtf( pow( deltas[X_AXIS], 2 ) + pow( deltas[Y_AXIS], 2 ) + pow( deltas[Z_AXIS], 2 ) );
504
505 // find distance unit vector
506 for (int i = 0; i < 3; i++)
507 unit_vec[i] = deltas[i] / millimeters_of_travel;
508
509 // Do not move faster than the configured cartesian limits
510 for (int axis = X_AXIS; axis <= Z_AXIS; axis++)
511 {
512 if ( max_speeds[axis] > 0 )
513 {
514 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
515
516 if (axis_speed > max_speeds[axis])
517 rate_mm_s *= ( max_speeds[axis] / axis_speed );
518 }
519 }
520
521 // find actuator position given cartesian position
522 arm_solution->cartesian_to_actuator( target, actuator_pos );
523
524 // check per-actuator speed limits
525 for (int actuator = 0; actuator <= 2; actuator++)
526 {
527 float actuator_rate = fabs(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * rate_mm_s / millimeters_of_travel;
528
529 if (actuator_rate > actuators[actuator]->max_rate)
530 rate_mm_s *= (actuators[actuator]->max_rate / actuator_rate);
531 }
532
533 // Append the block to the planner
534 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
535
536 // Update the last_milestone to the current target for the next time we use last_milestone
537 memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->last_milestone[] = target[];
538
539 }
540
541 // Append a move to the queue ( cutting it into segments if needed )
542 void Robot::append_line(Gcode* gcode, float target[], float rate_mm_s ){
543
544 // Find out the distance for this gcode
545 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 );
546
547 // We ignore non-moves ( for example, extruder moves are not XYZ moves )
548 if( gcode->millimeters_of_travel < 1e-8F ){
549 return;
550 }
551
552 gcode->millimeters_of_travel = sqrtf(gcode->millimeters_of_travel);
553
554 // Mark the gcode as having a known distance
555 this->distance_in_gcode_is_known( gcode );
556
557 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
558 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
559 // 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
560 uint16_t segments;
561
562 if(this->delta_segments_per_second > 1.0F) {
563 // enabled if set to something > 1, it is set to 0.0 by default
564 // segment based on current speed and requested segments per second
565 // the faster the travel speed the fewer segments needed
566 // NOTE rate is mm/sec and we take into account any speed override
567 float seconds = gcode->millimeters_of_travel / rate_mm_s;
568 segments= max(1, ceil(this->delta_segments_per_second * seconds));
569 // TODO if we are only moving in Z on a delta we don't really need to segment at all
570
571 }else{
572 if(this->mm_per_line_segment == 0.0F){
573 segments= 1; // don't split it up
574 }else{
575 segments = ceil( gcode->millimeters_of_travel/ this->mm_per_line_segment);
576 }
577 }
578
579 if (segments > 1)
580 {
581 // A vector to keep track of the endpoint of each segment
582 float segment_delta[3];
583 float segment_end[3];
584
585 // How far do we move each segment?
586 for (int i = X_AXIS; i <= Z_AXIS; i++)
587 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
588
589 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
590 // We always add another point after this loop so we stop at segments-1, ie i < segments
591 for (int i = 1; i < segments; i++)
592 {
593 for(int axis=X_AXIS; axis <= Z_AXIS; axis++ )
594 segment_end[axis] = last_milestone[axis] + segment_delta[axis];
595
596 // Append the end of this segment to the queue
597 this->append_milestone(segment_end, rate_mm_s);
598 }
599 }
600
601 // Append the end of this full move to the queue
602 this->append_milestone(target, rate_mm_s);
603
604 // if adding these blocks didn't start executing, do that now
605 THEKERNEL->conveyor->ensure_running();
606 }
607
608
609 // Append an arc to the queue ( cutting it into segments as needed )
610 void Robot::append_arc(Gcode* gcode, float target[], float offset[], float radius, bool is_clockwise ){
611
612 // Scary math
613 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
614 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
615 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
616 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
617 float r_axis1 = -offset[this->plane_axis_1];
618 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
619 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
620
621 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
622 float angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
623 if (angular_travel < 0) { angular_travel += 2*M_PI; }
624 if (is_clockwise) { angular_travel -= 2*M_PI; }
625
626 // Find the distance for this gcode
627 gcode->millimeters_of_travel = hypotf(angular_travel*radius, fabs(linear_travel));
628
629 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
630 if( gcode->millimeters_of_travel < 0.0001F ){ return; }
631
632 // Mark the gcode as having a known distance
633 this->distance_in_gcode_is_known( gcode );
634
635 // Figure out how many segments for this gcode
636 uint16_t segments = floor(gcode->millimeters_of_travel/this->mm_per_arc_segment);
637
638 float theta_per_segment = angular_travel/segments;
639 float linear_per_segment = linear_travel/segments;
640
641 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
642 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
643 r_T = [cos(phi) -sin(phi);
644 sin(phi) cos(phi] * r ;
645 For arc generation, the center of the circle is the axis of rotation and the radius vector is
646 defined from the circle center to the initial position. Each line segment is formed by successive
647 vector rotations. This requires only two cos() and sin() computations to form the rotation
648 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
649 all float numbers are single precision on the Arduino. (True float precision will not have
650 round off issues for CNC applications.) Single precision error can accumulate to be greater than
651 tool precision in some cases. Therefore, arc path correction is implemented.
652
653 Small angle approximation may be used to reduce computation overhead further. This approximation
654 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
655 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
656 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
657 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
658 issue for CNC machines with the single precision Arduino calculations.
659 This approximation also allows mc_arc to immediately insert a line segment into the planner
660 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
661 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
662 This is important when there are successive arc motions.
663 */
664 // Vector rotation matrix values
665 float cos_T = 1-0.5F*theta_per_segment*theta_per_segment; // Small angle approximation
666 float sin_T = theta_per_segment;
667
668 float arc_target[3];
669 float sin_Ti;
670 float cos_Ti;
671 float r_axisi;
672 uint16_t i;
673 int8_t count = 0;
674
675 // Initialize the linear axis
676 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
677
678 for (i = 1; i<segments; i++) { // Increment (segments-1)
679
680 if (count < this->arc_correction ) {
681 // Apply vector rotation matrix
682 r_axisi = r_axis0*sin_T + r_axis1*cos_T;
683 r_axis0 = r_axis0*cos_T - r_axis1*sin_T;
684 r_axis1 = r_axisi;
685 count++;
686 } else {
687 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
688 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
689 cos_Ti = cosf(i*theta_per_segment);
690 sin_Ti = sinf(i*theta_per_segment);
691 r_axis0 = -offset[this->plane_axis_0]*cos_Ti + offset[this->plane_axis_1]*sin_Ti;
692 r_axis1 = -offset[this->plane_axis_0]*sin_Ti - offset[this->plane_axis_1]*cos_Ti;
693 count = 0;
694 }
695
696 // Update arc_target location
697 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
698 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
699 arc_target[this->plane_axis_2] += linear_per_segment;
700
701 // Append this segment to the queue
702 this->append_milestone(arc_target, this->feed_rate / seconds_per_minute);
703
704 }
705
706 // Ensure last segment arrives at target location.
707 this->append_milestone(target, this->feed_rate / seconds_per_minute);
708 }
709
710 // Do the math for an arc and add it to the queue
711 void Robot::compute_arc(Gcode* gcode, float offset[], float target[]){
712
713 // Find the radius
714 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
715
716 // Set clockwise/counter-clockwise sign for mc_arc computations
717 bool is_clockwise = false;
718 if( this->motion_mode == MOTION_MODE_CW_ARC ){ is_clockwise = true; }
719
720 // Append arc
721 this->append_arc(gcode, target, offset, radius, is_clockwise );
722
723 }
724
725
726 float Robot::theta(float x, float y){
727 float t = atanf(x/fabs(y));
728 if (y>0) {return(t);} else {if (t>0){return(M_PI-t);} else {return(-M_PI-t);}}
729 }
730
731 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2){
732 this->plane_axis_0 = axis_0;
733 this->plane_axis_1 = axis_1;
734 this->plane_axis_2 = axis_2;
735 }
736
737