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