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