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