print out current machine coordinates with M114.1
[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 10
c3df978d
JM
11#include "mbed.h" // for us_ticker_read()
12
5673fe39 13#include <math.h>
4cff3ded
AW
14#include <string>
15using std::string;
5673fe39 16
4cff3ded 17#include "Planner.h"
3fceb8eb 18#include "Conveyor.h"
4cff3ded 19#include "Robot.h"
5673fe39
MM
20#include "nuts_bolts.h"
21#include "Pin.h"
22#include "StepperMotor.h"
23#include "Gcode.h"
5647f709 24#include "PublicDataRequest.h"
928467c0 25#include "PublicData.h"
4cff3ded
AW
26#include "arm_solutions/BaseSolution.h"
27#include "arm_solutions/CartesianSolution.h"
c41d6d95 28#include "arm_solutions/RotatableCartesianSolution.h"
2a06c415 29#include "arm_solutions/LinearDeltaSolution.h"
c52b8675 30#include "arm_solutions/RotatableDeltaSolution.h"
bdaaa75d 31#include "arm_solutions/HBotSolution.h"
fff1e42d 32#include "arm_solutions/CoreXZSolution.h"
1217e470 33#include "arm_solutions/MorganSCARASolution.h"
61134a65 34#include "StepTicker.h"
7af0714f
JM
35#include "checksumm.h"
36#include "utils.h"
8d54c34c 37#include "ConfigValue.h"
5966b7d0 38#include "libs/StreamOutput.h"
dd0a7cfa 39#include "StreamOutputPool.h"
928467c0 40#include "ExtruderPublicAccess.h"
38bf9a1c 41
78d0e16a
MM
42#define default_seek_rate_checksum CHECKSUM("default_seek_rate")
43#define default_feed_rate_checksum CHECKSUM("default_feed_rate")
44#define mm_per_line_segment_checksum CHECKSUM("mm_per_line_segment")
45#define delta_segments_per_second_checksum CHECKSUM("delta_segments_per_second")
46#define mm_per_arc_segment_checksum CHECKSUM("mm_per_arc_segment")
47#define arc_correction_checksum CHECKSUM("arc_correction")
48#define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
49#define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
50#define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
43424972
JM
51
52// arm solutions
78d0e16a
MM
53#define arm_solution_checksum CHECKSUM("arm_solution")
54#define cartesian_checksum CHECKSUM("cartesian")
55#define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
56#define rostock_checksum CHECKSUM("rostock")
2a06c415 57#define linear_delta_checksum CHECKSUM("linear_delta")
c52b8675 58#define rotatable_delta_checksum CHECKSUM("rotatable_delta")
78d0e16a
MM
59#define delta_checksum CHECKSUM("delta")
60#define hbot_checksum CHECKSUM("hbot")
61#define corexy_checksum CHECKSUM("corexy")
fff1e42d 62#define corexz_checksum CHECKSUM("corexz")
78d0e16a 63#define kossel_checksum CHECKSUM("kossel")
1217e470 64#define morgan_checksum CHECKSUM("morgan")
78d0e16a 65
78d0e16a
MM
66// new-style actuator stuff
67#define actuator_checksum CHEKCSUM("actuator")
68
69#define step_pin_checksum CHECKSUM("step_pin")
70#define dir_pin_checksum CHEKCSUM("dir_pin")
71#define en_pin_checksum CHECKSUM("en_pin")
72
73#define steps_per_mm_checksum CHECKSUM("steps_per_mm")
df6a30f2 74#define max_rate_checksum CHECKSUM("max_rate")
78d0e16a
MM
75
76#define alpha_checksum CHECKSUM("alpha")
77#define beta_checksum CHECKSUM("beta")
78#define gamma_checksum CHECKSUM("gamma")
79
38bf9a1c
JM
80#define NEXT_ACTION_DEFAULT 0
81#define NEXT_ACTION_DWELL 1
82#define NEXT_ACTION_GO_HOME 2
83
84#define MOTION_MODE_SEEK 0 // G0
85#define MOTION_MODE_LINEAR 1 // G1
86#define MOTION_MODE_CW_ARC 2 // G2
87#define MOTION_MODE_CCW_ARC 3 // G3
88#define MOTION_MODE_CANCEL 4 // G80
89
90#define PATH_CONTROL_MODE_EXACT_PATH 0
91#define PATH_CONTROL_MODE_EXACT_STOP 1
92#define PATH_CONTROL_MODE_CONTINOUS 2
93
94#define PROGRAM_FLOW_RUNNING 0
95#define PROGRAM_FLOW_PAUSED 1
96#define PROGRAM_FLOW_COMPLETED 2
97
98#define SPINDLE_DIRECTION_CW 0
99#define SPINDLE_DIRECTION_CCW 1
100
5fa0c173
PA
101#define ARC_ANGULAR_TRAVEL_EPSILON 5E-7 // Float (radians)
102
edac9072
AW
103// 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
104// It takes care of cutting arcs into segments, same thing for line that are too long
105
4710532a
JM
106Robot::Robot()
107{
a1b7e9f0 108 this->inch_mode = false;
0e8b102e 109 this->absolute_mode = true;
df27a6a3 110 this->motion_mode = MOTION_MODE_SEEK;
4cff3ded 111 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
df27a6a3 112 clear_vector(this->last_milestone);
3632a517 113 clear_vector(this->transformed_last_milestone);
0b804a41 114 this->arm_solution = NULL;
da947c62 115 seconds_per_minute = 60.0F;
fae93525 116 this->clearToolOffset();
3632a517 117 this->compensationTransform= nullptr;
807b9b57
JM
118 this->wcs_offsets.fill(wcs_t(0.0F,0.0F,0.0F));
119 this->g92_offset= wcs_t(0.0F,0.0F,0.0F);
4cff3ded
AW
120}
121
122//Called when the module has just been loaded
4710532a
JM
123void Robot::on_module_loaded()
124{
4cff3ded
AW
125 this->register_for_event(ON_GCODE_RECEIVED);
126
127 // Configuration
807b9b57 128 this->load_config();
da24d6ae
AW
129}
130
807b9b57
JM
131#define ACTUATOR_CHECKSUMS(X) { \
132 CHECKSUM(X "_step_pin"), \
133 CHECKSUM(X "_dir_pin"), \
134 CHECKSUM(X "_en_pin"), \
135 CHECKSUM(X "_steps_per_mm"), \
136 CHECKSUM(X "_max_rate") \
137}
5984acdf 138
807b9b57
JM
139void Robot::load_config()
140{
edac9072
AW
141 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
142 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
143 // To make adding those solution easier, they have their own, separate object.
5984acdf 144 // Here we read the config to find out which arm solution to use
0b804a41 145 if (this->arm_solution) delete this->arm_solution;
eda9facc 146 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
d149c730 147 // Note checksums are not const expressions when in debug mode, so don't use switch
98761c28 148 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
314ab8f7 149 this->arm_solution = new HBotSolution(THEKERNEL->config);
bdaaa75d 150
fff1e42d
JJ
151 } else if(solution_checksum == corexz_checksum) {
152 this->arm_solution = new CoreXZSolution(THEKERNEL->config);
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
c52b8675
DP
160 } else if(solution_checksum == rotatable_delta_checksum) {
161 this->arm_solution = new RotatableDeltaSolution(THEKERNEL->config);
162
1217e470
QH
163 } else if(solution_checksum == morgan_checksum) {
164 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
165
4710532a 166 } else if(solution_checksum == cartesian_checksum) {
314ab8f7 167 this->arm_solution = new CartesianSolution(THEKERNEL->config);
73a4e3c0 168
4710532a 169 } else {
314ab8f7 170 this->arm_solution = new CartesianSolution(THEKERNEL->config);
d149c730 171 }
73a4e3c0 172
6b661ab3
DP
173 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
174 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
175 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
176 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
177 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.5f)->as_number();
178 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
78d0e16a 179
6b661ab3
DP
180 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
181 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
182 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
feb204be 183
78d0e16a 184 // Make our 3 StepperMotors
807b9b57
JM
185 uint16_t const checksums[][5] = {
186 ACTUATOR_CHECKSUMS("alpha"),
187 ACTUATOR_CHECKSUMS("beta"),
188 ACTUATOR_CHECKSUMS("gamma"),
189#if MAX_ROBOT_ACTUATORS > 3
190 ACTUATOR_CHECKSUMS("delta"),
191 ACTUATOR_CHECKSUMS("epsilon"),
192 ACTUATOR_CHECKSUMS("zeta")
193#endif
194 };
195 constexpr size_t actuator_checksum_count = sizeof(checksums)/sizeof(checksums[0]);
196 static_assert(actuator_checksum_count >= k_max_actuators, "Robot checksum array too small for k_max_actuators");
197
198 size_t motor_count = std::min(this->arm_solution->get_actuator_count(), k_max_actuators);
199 for (size_t a = 0; a < motor_count; a++) {
200 Pin pins[3]; //step, dir, enable
201 for (size_t i = 0; i < 3; i++) {
202 pins[i].from_string(THEKERNEL->config->value(checksums[a][i])->by_default("nc")->as_string())->as_output();
203 }
204 actuators[a]= new StepperMotor(pins[0], pins[1], pins[2]);
78d0e16a 205
807b9b57
JM
206 actuators[a]->change_steps_per_mm(THEKERNEL->config->value(checksums[a][3])->by_default(a==2 ? 2560.0F : 80.0F)->as_number());
207 actuators[a]->set_max_rate(THEKERNEL->config->value(checksums[a][4])->by_default(30000.0F)->as_number());
208 }
a84f0186 209
dd0a7cfa 210 check_max_actuator_speeds(); // check the configs are sane
df6a30f2 211
975469ad
MM
212 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
213 // so the first move can be correct if homing is not performed
807b9b57 214 ActuatorCoordinates actuator_pos;
975469ad 215 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
807b9b57 216 for (size_t i = 0; i < actuators.size(); i++)
975469ad 217 actuators[i]->change_last_milestone(actuator_pos[i]);
5966b7d0
AT
218
219 //this->clearToolOffset();
4cff3ded
AW
220}
221
212caccd
JM
222void Robot::push_state()
223{
224 bool am= this->absolute_mode;
225 bool im= this->inch_mode;
226 saved_state_t s(this->feed_rate, this->seek_rate, am, im);
227 state_stack.push(s);
228}
229
230void Robot::pop_state()
231{
232 if(!state_stack.empty()) {
233 auto s= state_stack.top();
234 state_stack.pop();
235 this->feed_rate= std::get<0>(s);
236 this->seek_rate= std::get<1>(s);
237 this->absolute_mode= std::get<2>(s);
238 this->inch_mode= std::get<3>(s);
239 }
240}
241
dd0a7cfa
JM
242// this does a sanity check that actuator speeds do not exceed steps rate capability
243// we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
244void Robot::check_max_actuator_speeds()
245{
807b9b57
JM
246 for (size_t i = 0; i < actuators.size(); i++) {
247 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
248 if (step_freq > THEKERNEL->base_stepping_frequency) {
249 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
250 THEKERNEL->streams->printf("WARNING: actuator %c rate exceeds base_stepping_frequency * alpha_steps_per_mm: %f, setting to %f\n", 'A'+i, step_freq, actuators[i]->max_rate);
251 }
dd0a7cfa
JM
252 }
253}
254
4cff3ded 255//A GCode has been received
edac9072 256//See if the current Gcode line has some orders for us
4710532a
JM
257void Robot::on_gcode_received(void *argument)
258{
259 Gcode *gcode = static_cast<Gcode *>(argument);
6bc4a00a 260
23c90ba6 261 this->motion_mode = -1;
4cff3ded 262
4710532a
JM
263 //G-letter Gcodes are mostly what the Robot module is interrested in, other modules also catch the gcode event and do stuff accordingly
264 if( gcode->has_g) {
265 switch( gcode->g ) {
6e92ab91
JM
266 case 0: this->motion_mode = MOTION_MODE_SEEK; break;
267 case 1: this->motion_mode = MOTION_MODE_LINEAR; break;
268 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break;
269 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break;
c3df978d
JM
270 case 4: {
271 uint32_t delay_ms= 0;
272 if (gcode->has_letter('P')) {
273 delay_ms= gcode->get_int('P');
274 }
275 if (gcode->has_letter('S')) {
276 delay_ms += gcode->get_int('S') * 1000;
277 }
278 if (delay_ms > 0){
c3df978d
JM
279 // drain queue
280 THEKERNEL->conveyor->wait_for_empty_queue();
281 // wait for specified time
6ac0b51c 282 uint32_t start= us_ticker_read(); // mbed call
c3df978d
JM
283 while ((us_ticker_read() - start) < delay_ms*1000) {
284 THEKERNEL->call_event(ON_IDLE, this);
285 }
286 }
adba2978 287 }
6b661ab3 288 break;
807b9b57
JM
289
290 case 10: // G10 L2 Pn Xn Yn Zn set WCS
291 // TODO implement G10 L20
292 if(gcode->has_letter('L') && gcode->get_int('L') == 2 && gcode->has_letter('P')) {
293 size_t n= gcode->get_uint('P');
294 if(n == 0) n= current_wcs; // set current coordinate system
295 else --n;
296 if(n < k_max_wcs) {
297 float x, y, z;
298 std::tie(x, y, z)= wcs_offsets[n];
299 if(gcode->has_letter('X')) x= this->to_millimeters(gcode->get_value('X'));
300 if(gcode->has_letter('Y')) y= this->to_millimeters(gcode->get_value('Y'));
301 if(gcode->has_letter('Z')) z= this->to_millimeters(gcode->get_value('Z'));
302 wcs_offsets[n]= wcs_t(x, y, z);
303 }
304 }
305 break;
306
6e92ab91
JM
307 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
308 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
309 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
310 case 20: this->inch_mode = true; break;
311 case 21: this->inch_mode = false; break;
807b9b57
JM
312
313 case 54: case 55: case 56: case 57: case 58: case 59:
314 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
315 current_wcs= gcode->g - 54;
316 if(gcode->g == 59 && gcode->subcode > 0) {
317 current_wcs += gcode->subcode;
318 if(current_wcs >= k_max_wcs) current_wcs= k_max_wcs-1;
319 }
320 break;
321
6e92ab91
JM
322 case 90: this->absolute_mode = true; break;
323 case 91: this->absolute_mode = false; break;
807b9b57 324
0b804a41 325 case 92: {
807b9b57
JM
326 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
327 g92_offset= wcs_t(0,0,0);
cef9acea 328
4710532a 329 } else {
807b9b57
JM
330 float x, y, z;
331 std::tie(x, y, z)= g92_offset;
1077818f
JM
332 if(gcode->has_letter('X')) x= last_milestone[0] - to_millimeters(gcode->get_value('X'));
333 if(gcode->has_letter('Y')) y= last_milestone[1] - to_millimeters(gcode->get_value('Y'));
334 if(gcode->has_letter('Z')) z= last_milestone[2] - to_millimeters(gcode->get_value('Z'));
807b9b57 335 g92_offset= wcs_t(x, y, z);
6bc4a00a 336 }
78d0e16a 337 return;
4710532a
JM
338 }
339 }
67a649dd 340
4710532a
JM
341 } else if( gcode->has_m) {
342 switch( gcode->m ) {
807b9b57
JM
343 case 2: // M2 end of program
344 current_wcs= 0;
345 absolute_mode= true;
346 motion_mode = MOTION_MODE_LINEAR; // feed
347 break;
348
0fb5b438 349 case 92: // M92 - set steps per mm
0fb5b438 350 if (gcode->has_letter('X'))
78d0e16a 351 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
0fb5b438 352 if (gcode->has_letter('Y'))
78d0e16a 353 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
0fb5b438 354 if (gcode->has_letter('Z'))
78d0e16a 355 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
7369629d
MM
356 if (gcode->has_letter('F'))
357 seconds_per_minute = gcode->get_value('F');
78d0e16a
MM
358
359 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 360 gcode->add_nl = true;
dd0a7cfa 361 check_max_actuator_speeds();
0fb5b438 362 return;
562db364 363
4710532a 364 case 114: {
58c32991 365 char buf[64];
807b9b57
JM
366 int n= 0;
367 if(gcode->subcode == 0) { // M114 print WCS
368 n = snprintf(buf, sizeof(buf), "C: X:%1.3f Y:%1.3f Z:%1.3f",
369 from_millimeters(this->last_milestone[0]),
370 from_millimeters(this->last_milestone[1]),
371 from_millimeters(this->last_milestone[2]));
372
373 }else if(gcode->subcode == 1) { // M114.1 print Machine coordinate system
374 // TODO figure this out
375 n = snprintf(buf, sizeof(buf), "X:%1.3f Y:%1.3f Z:%1.3f",
14139287
JM
376 from_millimeters(this->last_milestone[0]) - (std::get<0>(wcs_offsets[current_wcs]) + std::get<0>(g92_offset)),
377 from_millimeters(this->last_milestone[1]) - (std::get<1>(wcs_offsets[current_wcs]) + std::get<1>(g92_offset)),
378 from_millimeters(this->last_milestone[2]) - (std::get<2>(wcs_offsets[current_wcs]) + std::get<2>(g92_offset)) );
807b9b57
JM
379
380 }else if(gcode->subcode == 2) { // M114.2 print realtime actuator position
381 n = snprintf(buf, sizeof(buf), "A:%1.3f B:%1.3f C:%1.3f",
58c32991
JM
382 actuators[X_AXIS]->get_current_position(),
383 actuators[Y_AXIS]->get_current_position(),
384 actuators[Z_AXIS]->get_current_position() );
807b9b57
JM
385 }
386 if(n > 0)
387 gcode->txt_after_ok.append(buf, n);
4710532a
JM
388 }
389 return;
33e4cc02 390
212caccd
JM
391 case 120: // push state
392 push_state();
393 break;
562db364
JM
394
395 case 121: // pop state
212caccd 396 pop_state();
562db364
JM
397 break;
398
83488642
JM
399 case 203: // M203 Set maximum feedrates in mm/sec
400 if (gcode->has_letter('X'))
4710532a 401 this->max_speeds[X_AXIS] = gcode->get_value('X');
83488642 402 if (gcode->has_letter('Y'))
4710532a 403 this->max_speeds[Y_AXIS] = gcode->get_value('Y');
83488642 404 if (gcode->has_letter('Z'))
4710532a 405 this->max_speeds[Z_AXIS] = gcode->get_value('Z');
807b9b57
JM
406 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
407 if (gcode->has_letter('A' + i))
408 actuators[i]->set_max_rate(gcode->get_value('A' + i));
409 }
dd0a7cfa
JM
410 check_max_actuator_speeds();
411
928467c0 412 if(gcode->get_num_args() == 0) {
807b9b57
JM
413 gcode->stream->printf("X:%g Y:%g Z:%g",
414 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
415 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
416 gcode->stream->printf(" %c : %g", 'A' + i, actuators[i]->get_max_rate()); //xxx
417 }
928467c0
JM
418 gcode->add_nl = true;
419 }
83488642
JM
420 break;
421
c5fe1787 422 case 204: // M204 Snnn - set acceleration to nnn, Znnn sets z acceleration
4710532a 423 if (gcode->has_letter('S')) {
4710532a 424 float acc = gcode->get_value('S'); // mm/s^2
d4ee6ee2 425 // enforce minimum
da947c62
MM
426 if (acc < 1.0F)
427 acc = 1.0F;
4710532a 428 THEKERNEL->planner->acceleration = acc;
d4ee6ee2 429 }
c5fe1787 430 if (gcode->has_letter('Z')) {
c5fe1787
JM
431 float acc = gcode->get_value('Z'); // mm/s^2
432 // enforce positive
433 if (acc < 0.0F)
434 acc = 0.0F;
435 THEKERNEL->planner->z_acceleration = acc;
436 }
d4ee6ee2
JM
437 break;
438
9502f9d5 439 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
4710532a
JM
440 if (gcode->has_letter('X')) {
441 float jd = gcode->get_value('X');
d4ee6ee2 442 // enforce minimum
8b69c90d
JM
443 if (jd < 0.0F)
444 jd = 0.0F;
4710532a 445 THEKERNEL->planner->junction_deviation = jd;
d4ee6ee2 446 }
107df03f
JM
447 if (gcode->has_letter('Z')) {
448 float jd = gcode->get_value('Z');
449 // enforce minimum, -1 disables it and uses regular junction deviation
450 if (jd < -1.0F)
451 jd = -1.0F;
452 THEKERNEL->planner->z_junction_deviation = jd;
453 }
4710532a
JM
454 if (gcode->has_letter('S')) {
455 float mps = gcode->get_value('S');
8b69c90d
JM
456 // enforce minimum
457 if (mps < 0.0F)
458 mps = 0.0F;
4710532a 459 THEKERNEL->planner->minimum_planner_speed = mps;
8b69c90d 460 }
9502f9d5 461 if (gcode->has_letter('Y')) {
807b9b57 462 actuators[0]->default_minimum_actuator_rate = gcode->get_value('Y');
9502f9d5 463 }
d4ee6ee2 464 break;
98761c28 465
7369629d 466 case 220: // M220 - speed override percentage
4710532a 467 if (gcode->has_letter('S')) {
1ad23cd3 468 float factor = gcode->get_value('S');
98761c28 469 // enforce minimum 10% speed
da947c62
MM
470 if (factor < 10.0F)
471 factor = 10.0F;
472 // enforce maximum 10x speed
473 if (factor > 1000.0F)
474 factor = 1000.0F;
475
476 seconds_per_minute = 6000.0F / factor;
adba2978 477 }else{
9ef9f45b 478 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
7369629d 479 }
b4f56013 480 break;
ec4773e5 481
494dc541 482 case 400: // wait until all moves are done up to this point
314ab8f7 483 THEKERNEL->conveyor->wait_for_empty_queue();
494dc541
JM
484 break;
485
33e4cc02 486 case 500: // M500 saves some volatile settings to config override file
b7cd847e 487 case 503: { // M503 just prints the settings
78d0e16a 488 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);
c5fe1787 489 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", THEKERNEL->planner->acceleration, THEKERNEL->planner->z_acceleration);
c9cc5e06 490 gcode->stream->printf(";X- Junction Deviation, Z- Z junction deviation, S - Minimum Planner speed mm/sec:\nM205 X%1.5f Z%1.5f S%1.5f\n", THEKERNEL->planner->junction_deviation, THEKERNEL->planner->z_junction_deviation, THEKERNEL->planner->minimum_planner_speed);
807b9b57
JM
491 gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f",
492 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
493 for (size_t i=0; i < 3 && i < actuators.size(); i++){
494 gcode->stream->printf(" %c%1.5f", 'A' + i, actuators[i]->get_max_rate());
495 }
496 gcode->stream->printf("\n");
b7cd847e
JM
497
498 // get or save any arm solution specific optional values
499 BaseSolution::arm_options_t options;
500 if(arm_solution->get_optional(options) && !options.empty()) {
501 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
4710532a 502 for(auto &i : options) {
b7cd847e
JM
503 gcode->stream->printf(" %c%1.4f", i.first, i.second);
504 }
505 gcode->stream->printf("\n");
506 }
6e92ab91 507
807b9b57
JM
508 // save wcs_offsets and current_wcs
509 // TODO this may need to be done whenever they change to be compliant
510 gcode->stream->printf(";WCS settings\n");
511 gcode->stream->printf("G5%c", std::min(current_wcs, (uint8_t)(5 + '4')));
512 if(current_wcs >= 6) {
513 gcode->stream->printf(".%c\n", '1' + (current_wcs-5));
514 }else{
515 gcode->stream->printf("\n");
516 }
517 int n= 1;
518 for(auto &i : wcs_offsets) {
519 if(i != wcs_t(0,0,0)) {
520 float x, y, z;
521 std::tie(x, y, z) = i;
522 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f\n", n, x, y, z);
523 }
524 ++n;
525 }
b7cd847e 526 }
67a649dd
JM
527
528 if(gcode->m == 503) {
529 // just print the G92 setting as it is not saved
530 if(g92_offset != wcs_t(0,0,0)) {
531 float x, y, z;
532 std::tie(x, y, z) = g92_offset;
533 gcode->stream->printf("G92 X%f Y%f Z%f ; NOT SAVED\n", x, y, z);
534 }
535 }
807b9b57 536 break;
33e4cc02 537
b7cd847e 538 case 665: { // M665 set optional arm solution variables based on arm solution.
ebc75fc6
JM
539 // the parameter args could be any letter each arm solution only accepts certain ones
540 BaseSolution::arm_options_t options= gcode->get_args();
541 options.erase('S'); // don't include the S
542 options.erase('U'); // don't include the U
543 if(options.size() > 0) {
544 // set the specified options
545 arm_solution->set_optional(options);
546 }
547 options.clear();
b7cd847e 548 if(arm_solution->get_optional(options)) {
ebc75fc6 549 // foreach optional value
4710532a 550 for(auto &i : options) {
b7cd847e
JM
551 // print all current values of supported options
552 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
5523c05d 553 gcode->add_nl = true;
ec4773e5
JM
554 }
555 }
ec4773e5 556
4a839bea 557 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
4710532a 558 this->delta_segments_per_second = gcode->get_value('S');
4a839bea
JM
559 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
560
561 }else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
562 this->mm_per_line_segment = gcode->get_value('U');
563 this->delta_segments_per_second = 0;
564 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
ec29d378 565 }
4a839bea 566
ec4773e5 567 break;
b7cd847e 568 }
6989211c 569 }
494dc541
JM
570 }
571
c83887ea
MM
572 if( this->motion_mode < 0)
573 return;
6bc4a00a 574
4710532a 575 //Get parameters
1ad23cd3 576 float target[3], offset[3];
c2885de8 577 clear_vector(offset);
6bc4a00a 578
2ba859c9 579 memcpy(target, this->last_milestone, sizeof(target)); //default to last target
6bc4a00a 580
4710532a
JM
581 for(char letter = 'I'; letter <= 'K'; letter++) {
582 if( gcode->has_letter(letter) ) {
583 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
c2885de8
JM
584 }
585 }
4710532a
JM
586 for(char letter = 'X'; letter <= 'Z'; letter++) {
587 if( gcode->has_letter(letter) ) {
c7689006 588 target[letter - 'X'] = this->to_millimeters(gcode->get_value(letter)) + (this->absolute_mode ? this->toolOffset[letter - 'X'] : target[letter - 'X']);
c2885de8
JM
589 }
590 }
6bc4a00a 591
4710532a 592 if( gcode->has_letter('F') ) {
7369629d 593 if( this->motion_mode == MOTION_MODE_SEEK )
da947c62 594 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 595 else
da947c62 596 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
7369629d 597 }
6bc4a00a 598
4cff3ded 599 //Perform any physical actions
fae93525
JM
600 switch(this->motion_mode) {
601 case MOTION_MODE_CANCEL: break;
602 case MOTION_MODE_SEEK : this->append_line(gcode, target, this->seek_rate / seconds_per_minute ); break;
603 case MOTION_MODE_LINEAR: this->append_line(gcode, target, this->feed_rate / seconds_per_minute ); break;
604 case MOTION_MODE_CW_ARC:
605 case MOTION_MODE_CCW_ARC: this->compute_arc(gcode, offset, target ); break;
4cff3ded 606 }
13e4a3f9 607
fae93525 608 // last_milestone was set to target in append_milestone, no need to do it again
4cff3ded 609
edac9072
AW
610}
611
5984acdf 612// We received a new gcode, and one of the functions
edac9072
AW
613// determined the distance for that given gcode. So now we can attach this gcode to the right block
614// and continue
4710532a
JM
615void Robot::distance_in_gcode_is_known(Gcode *gcode)
616{
edac9072 617 //If the queue is empty, execute immediatly, otherwise attach to the last added block
e0ee24ed 618 THEKERNEL->conveyor->append_gcode(gcode);
edac9072
AW
619}
620
cef9acea
JM
621// reset the position for all axis (used in homing for delta as last_milestone may be bogus)
622void Robot::reset_axis_position(float x, float y, float z)
623{
624 this->last_milestone[X_AXIS] = x;
625 this->last_milestone[Y_AXIS] = y;
626 this->last_milestone[Z_AXIS] = z;
3632a517
JM
627 this->transformed_last_milestone[X_AXIS] = x;
628 this->transformed_last_milestone[Y_AXIS] = y;
629 this->transformed_last_milestone[Z_AXIS] = z;
cef9acea 630
807b9b57 631 ActuatorCoordinates actuator_pos;
cef9acea 632 arm_solution->cartesian_to_actuator(this->last_milestone, actuator_pos);
807b9b57 633 for (size_t i = 0; i < actuators.size(); i++)
cef9acea
JM
634 actuators[i]->change_last_milestone(actuator_pos[i]);
635}
636
807b9b57 637// Reset the position for an axis (used in homing)
4710532a
JM
638void Robot::reset_axis_position(float position, int axis)
639{
2ba859c9 640 this->last_milestone[axis] = position;
3632a517 641 this->transformed_last_milestone[axis] = position;
29c28822 642
807b9b57 643 ActuatorCoordinates actuator_pos;
cef9acea 644 arm_solution->cartesian_to_actuator(this->last_milestone, actuator_pos);
29c28822 645
807b9b57 646 for (size_t i = 0; i < actuators.size(); i++)
29c28822 647 actuators[i]->change_last_milestone(actuator_pos[i]);
4cff3ded
AW
648}
649
728477c4 650// Use FK to find out where actuator is and reset lastmilestone to match
728477c4
JM
651void Robot::reset_position_from_current_actuator_position()
652{
807b9b57
JM
653 ActuatorCoordinates actuator_pos;
654 for (size_t i = 0; i < actuators.size(); i++) {
655 actuator_pos[i] = actuators[i]->get_current_position();
656 }
58c32991 657 arm_solution->actuator_to_cartesian(actuator_pos, this->last_milestone);
4befe777 658 memcpy(this->transformed_last_milestone, this->last_milestone, sizeof(this->transformed_last_milestone));
cf91d4f3
JM
659
660 // now reset actuator correctly, NOTE this may lose a little precision
661 arm_solution->cartesian_to_actuator(this->last_milestone, actuator_pos);
807b9b57 662 for (size_t i = 0; i < actuators.size(); i++)
cf91d4f3 663 actuators[i]->change_last_milestone(actuator_pos[i]);
728477c4 664}
edac9072 665
4cff3ded 666// Convert target from millimeters to steps, and append this to the planner
d2adef5e 667void Robot::append_milestone(Gcode *gcode, float target[], float rate_mm_s)
df6a30f2 668{
1ad23cd3 669 float deltas[3];
df6a30f2 670 float unit_vec[3];
807b9b57 671 ActuatorCoordinates actuator_pos;
3632a517 672 float transformed_target[3]; // adjust target for bed compensation
df6a30f2
MM
673 float millimeters_of_travel;
674
3632a517
JM
675 // unity transform by default
676 memcpy(transformed_target, target, sizeof(transformed_target));
5e45206a 677
3632a517
JM
678 // check function pointer and call if set to transform the target to compensate for bed
679 if(compensationTransform) {
680 // some compensation strategies can transform XYZ, some just change Z
681 compensationTransform(transformed_target);
33742399 682 }
ff7e9858 683
807b9b57
JM
684 // apply wcs offsets and g92 offset
685 transformed_target[0] += (std::get<0>(wcs_offsets[current_wcs]) + std::get<0>(g92_offset));
686 transformed_target[1] += (std::get<1>(wcs_offsets[current_wcs]) + std::get<1>(g92_offset));
687 transformed_target[2] += (std::get<2>(wcs_offsets[current_wcs]) + std::get<2>(g92_offset));
688
3632a517
JM
689 // find distance moved by each axis, use transformed target from last_transformed_target
690 for (int axis = X_AXIS; axis <= Z_AXIS; axis++){
691 deltas[axis] = transformed_target[axis] - transformed_last_milestone[axis];
692 }
693 // store last transformed
694 memcpy(this->transformed_last_milestone, transformed_target, sizeof(this->transformed_last_milestone));
aab6cbba 695
edac9072 696 // Compute how long this move moves, so we can attach it to the block for later use
869acfb8 697 millimeters_of_travel = sqrtf( powf( deltas[X_AXIS], 2 ) + powf( deltas[Y_AXIS], 2 ) + powf( deltas[Z_AXIS], 2 ) );
df6a30f2
MM
698
699 // find distance unit vector
700 for (int i = 0; i < 3; i++)
701 unit_vec[i] = deltas[i] / millimeters_of_travel;
702
703 // Do not move faster than the configured cartesian limits
4710532a
JM
704 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
705 if ( max_speeds[axis] > 0 ) {
da947c62 706 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
df6a30f2
MM
707
708 if (axis_speed > max_speeds[axis])
da947c62 709 rate_mm_s *= ( max_speeds[axis] / axis_speed );
7b470506
AW
710 }
711 }
4cff3ded 712
5e45206a 713 // find actuator position given cartesian position, use actual adjusted target
3632a517 714 arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
df6a30f2 715
928467c0 716 float isecs= rate_mm_s / millimeters_of_travel;
df6a30f2 717 // check per-actuator speed limits
807b9b57 718 for (size_t actuator = 0; actuator < actuators.size(); actuator++) {
928467c0
JM
719 float actuator_rate = fabsf(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * isecs;
720 if (actuator_rate > actuators[actuator]->get_max_rate()){
3494f3d0 721 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
4caeff22 722 isecs= rate_mm_s / millimeters_of_travel;
928467c0
JM
723 }
724 }
725
edac9072 726 // Append the block to the planner
da947c62 727 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
4cff3ded 728
5e45206a 729 // Update the last_milestone to the current target for the next time we use last_milestone, use the requested target not the adjusted one
c2885de8 730 memcpy(this->last_milestone, target, sizeof(this->last_milestone)); // this->last_milestone[] = target[];
4cff3ded
AW
731
732}
733
edac9072 734// Append a move to the queue ( cutting it into segments if needed )
4710532a
JM
735void Robot::append_line(Gcode *gcode, float target[], float rate_mm_s )
736{
edac9072 737 // Find out the distance for this gcode
a9d299ab 738 // NOTE we need to do sqrt here as this setting of millimeters_of_travel is used by extruder and other modules even if there is no XYZ move
3b4b05b8 739 gcode->millimeters_of_travel = sqrtf(powf( target[X_AXIS] - this->last_milestone[X_AXIS], 2 ) + powf( target[Y_AXIS] - this->last_milestone[Y_AXIS], 2 ) + powf( target[Z_AXIS] - this->last_milestone[Z_AXIS], 2 ));
4cff3ded 740
3b4b05b8
JM
741 // We ignore non- XYZ moves ( for example, extruder moves are not XYZ moves )
742 if( gcode->millimeters_of_travel < 0.00001F ) {
95b4885b
JM
743 return;
744 }
436a2cd1 745
edac9072 746 // Mark the gcode as having a known distance
5dcb2ff3 747 this->distance_in_gcode_is_known( gcode );
436a2cd1 748
d2adef5e
JM
749 // if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit
750 // Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
751 // We also check we are not exceeding the E max_speed for the current extruder
752 // We ask Extruder to do all the work, but as Extruder won't even see this gcode until after it has been planned
753 // we need to ask it now passing in the relevant data.
754 // NOTE we need to do this before we segment the line (for deltas)
755 if(gcode->has_letter('E')) {
756 float data[2];
757 data[0]= gcode->get_value('E'); // E target (maybe absolute or relative)
758 data[1]= rate_mm_s / gcode->millimeters_of_travel; // inverted seconds for the move
759 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
760 rate_mm_s *= data[1];
761 //THEKERNEL->streams->printf("Extruder has changed the rate by %f to %f\n", data[1], rate_mm_s);
762 }
763 }
764
4a0c8e14
JM
765 // We cut the line into smaller segments. This is not usefull in a cartesian robot, but necessary for robots with rotational axes.
766 // In cartesian robot, a high "mm_per_line_segment" setting will prevent waste.
3b4b05b8
JM
767 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
768 // 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 769 uint16_t segments;
5984acdf 770
c2885de8 771 if(this->delta_segments_per_second > 1.0F) {
4a0c8e14
JM
772 // enabled if set to something > 1, it is set to 0.0 by default
773 // segment based on current speed and requested segments per second
774 // the faster the travel speed the fewer segments needed
775 // NOTE rate is mm/sec and we take into account any speed override
da947c62 776 float seconds = gcode->millimeters_of_travel / rate_mm_s;
9502f9d5 777 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
4a0c8e14 778 // TODO if we are only moving in Z on a delta we don't really need to segment at all
5984acdf 779
4710532a
JM
780 } else {
781 if(this->mm_per_line_segment == 0.0F) {
782 segments = 1; // don't split it up
783 } else {
9502f9d5 784 segments = ceilf( gcode->millimeters_of_travel / this->mm_per_line_segment);
4a0c8e14
JM
785 }
786 }
5984acdf 787
4710532a 788 if (segments > 1) {
2ba859c9
MM
789 // A vector to keep track of the endpoint of each segment
790 float segment_delta[3];
791 float segment_end[3];
792
793 // How far do we move each segment?
9fff6045 794 for (int i = X_AXIS; i <= Z_AXIS; i++)
2ba859c9 795 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
4cff3ded 796
c8e0fb15
MM
797 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
798 // We always add another point after this loop so we stop at segments-1, ie i < segments
4710532a 799 for (int i = 1; i < segments; i++) {
73706276 800 if(THEKERNEL->is_halted()) return; // don't queue any more segments
4710532a 801 for(int axis = X_AXIS; axis <= Z_AXIS; axis++ )
2ba859c9
MM
802 segment_end[axis] = last_milestone[axis] + segment_delta[axis];
803
804 // Append the end of this segment to the queue
928467c0 805 this->append_milestone(gcode, segment_end, rate_mm_s);
2ba859c9 806 }
4cff3ded 807 }
5984acdf
MM
808
809 // Append the end of this full move to the queue
928467c0 810 this->append_milestone(gcode, target, rate_mm_s);
2134bcf2
MM
811
812 // if adding these blocks didn't start executing, do that now
813 THEKERNEL->conveyor->ensure_running();
4cff3ded
AW
814}
815
4cff3ded 816
edac9072 817// Append an arc to the queue ( cutting it into segments as needed )
4710532a
JM
818void Robot::append_arc(Gcode *gcode, float target[], float offset[], float radius, bool is_clockwise )
819{
aab6cbba 820
edac9072 821 // Scary math
2ba859c9
MM
822 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
823 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
824 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
1ad23cd3
MM
825 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
826 float r_axis1 = -offset[this->plane_axis_1];
827 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
828 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
aab6cbba 829
51871fb8 830 // Patch from GRBL Firmware - Christoph Baumann 04072015
aab6cbba 831 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
5fa0c173
PA
832 float angular_travel = atan2(r_axis0*rt_axis1-r_axis1*rt_axis0, r_axis0*rt_axis0+r_axis1*rt_axis1);
833 if (is_clockwise) { // Correct atan2 output per direction
834 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2*M_PI; }
835 } else {
836 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2*M_PI; }
4710532a 837 }
aab6cbba 838
edac9072 839 // Find the distance for this gcode
4710532a 840 gcode->millimeters_of_travel = hypotf(angular_travel * radius, fabs(linear_travel));
436a2cd1 841
edac9072 842 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
3b4b05b8 843 if( gcode->millimeters_of_travel < 0.00001F ) {
4710532a
JM
844 return;
845 }
5dcb2ff3 846
edac9072 847 // Mark the gcode as having a known distance
d149c730 848 this->distance_in_gcode_is_known( gcode );
5984acdf
MM
849
850 // Figure out how many segments for this gcode
c8f4ee77 851 uint16_t segments = floorf(gcode->millimeters_of_travel / this->mm_per_arc_segment);
aab6cbba 852
4710532a
JM
853 float theta_per_segment = angular_travel / segments;
854 float linear_per_segment = linear_travel / segments;
aab6cbba
AW
855
856 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
857 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
858 r_T = [cos(phi) -sin(phi);
859 sin(phi) cos(phi] * r ;
860 For arc generation, the center of the circle is the axis of rotation and the radius vector is
861 defined from the circle center to the initial position. Each line segment is formed by successive
862 vector rotations. This requires only two cos() and sin() computations to form the rotation
863 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1ad23cd3 864 all float numbers are single precision on the Arduino. (True float precision will not have
aab6cbba
AW
865 round off issues for CNC applications.) Single precision error can accumulate to be greater than
866 tool precision in some cases. Therefore, arc path correction is implemented.
867
868 Small angle approximation may be used to reduce computation overhead further. This approximation
869 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
870 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
871 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
872 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
873 issue for CNC machines with the single precision Arduino calculations.
874 This approximation also allows mc_arc to immediately insert a line segment into the planner
875 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
876 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
877 This is important when there are successive arc motions.
878 */
879 // Vector rotation matrix values
4710532a 880 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1ad23cd3 881 float sin_T = theta_per_segment;
aab6cbba 882
1ad23cd3
MM
883 float arc_target[3];
884 float sin_Ti;
885 float cos_Ti;
886 float r_axisi;
aab6cbba
AW
887 uint16_t i;
888 int8_t count = 0;
889
890 // Initialize the linear axis
2ba859c9 891 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
aab6cbba 892
4710532a 893 for (i = 1; i < segments; i++) { // Increment (segments-1)
73706276 894 if(THEKERNEL->is_halted()) return; // don't queue any more segments
aab6cbba 895
b66fb830 896 if (count < this->arc_correction ) {
4710532a
JM
897 // Apply vector rotation matrix
898 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
899 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
900 r_axis1 = r_axisi;
901 count++;
aab6cbba 902 } else {
4710532a
JM
903 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
904 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
905 cos_Ti = cosf(i * theta_per_segment);
906 sin_Ti = sinf(i * theta_per_segment);
907 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
908 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
909 count = 0;
aab6cbba
AW
910 }
911
912 // Update arc_target location
913 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
914 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
915 arc_target[this->plane_axis_2] += linear_per_segment;
edac9072
AW
916
917 // Append this segment to the queue
928467c0 918 this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
aab6cbba
AW
919
920 }
edac9072 921
aab6cbba 922 // Ensure last segment arrives at target location.
928467c0 923 this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute);
aab6cbba
AW
924}
925
edac9072 926// Do the math for an arc and add it to the queue
4710532a
JM
927void Robot::compute_arc(Gcode *gcode, float offset[], float target[])
928{
aab6cbba
AW
929
930 // Find the radius
13addf09 931 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
aab6cbba
AW
932
933 // Set clockwise/counter-clockwise sign for mc_arc computations
934 bool is_clockwise = false;
4710532a
JM
935 if( this->motion_mode == MOTION_MODE_CW_ARC ) {
936 is_clockwise = true;
937 }
aab6cbba
AW
938
939 // Append arc
436a2cd1 940 this->append_arc(gcode, target, offset, radius, is_clockwise );
aab6cbba
AW
941
942}
943
944
4710532a
JM
945float Robot::theta(float x, float y)
946{
947 float t = atanf(x / fabs(y));
948 if (y > 0) {
949 return(t);
950 } else {
951 if (t > 0) {
952 return(M_PI - t);
953 } else {
954 return(-M_PI - t);
955 }
956 }
4cff3ded
AW
957}
958
4710532a
JM
959void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
960{
4cff3ded
AW
961 this->plane_axis_0 = axis_0;
962 this->plane_axis_1 = axis_1;
963 this->plane_axis_2 = axis_2;
964}
965
fae93525 966void Robot::clearToolOffset()
4710532a 967{
fae93525
JM
968 memset(this->toolOffset, 0, sizeof(this->toolOffset));
969}
970
971void Robot::setToolOffset(const float offset[3])
972{
fae93525 973 memcpy(this->toolOffset, offset, sizeof(this->toolOffset));
5966b7d0
AT
974}
975