M306 for a rotary delta calculates the theta offset by comparing the current angle...
[clinton/Smoothieware.git] / src / modules / robot / Robot.cpp
1 /*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl) with additions from Sungeun K. Jeon (https://github.com/chamnit/grbl)
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "libs/Module.h"
9 #include "libs/Kernel.h"
10
11 #include "mbed.h" // for us_ticker_read()
12
13 #include <math.h>
14 #include <string>
15 using std::string;
16
17 #include "Planner.h"
18 #include "Conveyor.h"
19 #include "Robot.h"
20 #include "nuts_bolts.h"
21 #include "Pin.h"
22 #include "StepperMotor.h"
23 #include "Gcode.h"
24 #include "PublicDataRequest.h"
25 #include "PublicData.h"
26 #include "arm_solutions/BaseSolution.h"
27 #include "arm_solutions/CartesianSolution.h"
28 #include "arm_solutions/RotatableCartesianSolution.h"
29 #include "arm_solutions/LinearDeltaSolution.h"
30 #include "arm_solutions/RotaryDeltaSolution.h"
31 #include "arm_solutions/HBotSolution.h"
32 #include "arm_solutions/CoreXZSolution.h"
33 #include "arm_solutions/MorganSCARASolution.h"
34 #include "StepTicker.h"
35 #include "checksumm.h"
36 #include "utils.h"
37 #include "ConfigValue.h"
38 #include "libs/StreamOutput.h"
39 #include "StreamOutputPool.h"
40 #include "ExtruderPublicAccess.h"
41 #include "GcodeDispatch.h"
42
43
44 #define default_seek_rate_checksum CHECKSUM("default_seek_rate")
45 #define default_feed_rate_checksum CHECKSUM("default_feed_rate")
46 #define mm_per_line_segment_checksum CHECKSUM("mm_per_line_segment")
47 #define delta_segments_per_second_checksum CHECKSUM("delta_segments_per_second")
48 #define mm_per_arc_segment_checksum CHECKSUM("mm_per_arc_segment")
49 #define arc_correction_checksum CHECKSUM("arc_correction")
50 #define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
51 #define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
52 #define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
53
54 // arm solutions
55 #define arm_solution_checksum CHECKSUM("arm_solution")
56 #define cartesian_checksum CHECKSUM("cartesian")
57 #define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
58 #define rostock_checksum CHECKSUM("rostock")
59 #define linear_delta_checksum CHECKSUM("linear_delta")
60 #define rotary_delta_checksum CHECKSUM("rotary_delta")
61 #define delta_checksum CHECKSUM("delta")
62 #define hbot_checksum CHECKSUM("hbot")
63 #define corexy_checksum CHECKSUM("corexy")
64 #define corexz_checksum CHECKSUM("corexz")
65 #define kossel_checksum CHECKSUM("kossel")
66 #define morgan_checksum CHECKSUM("morgan")
67
68 // new-style actuator stuff
69 #define actuator_checksum CHEKCSUM("actuator")
70
71 #define step_pin_checksum CHECKSUM("step_pin")
72 #define dir_pin_checksum CHEKCSUM("dir_pin")
73 #define en_pin_checksum CHECKSUM("en_pin")
74
75 #define steps_per_mm_checksum CHECKSUM("steps_per_mm")
76 #define max_rate_checksum CHECKSUM("max_rate")
77
78 #define alpha_checksum CHECKSUM("alpha")
79 #define beta_checksum CHECKSUM("beta")
80 #define gamma_checksum CHECKSUM("gamma")
81
82 #define NEXT_ACTION_DEFAULT 0
83 #define NEXT_ACTION_DWELL 1
84 #define NEXT_ACTION_GO_HOME 2
85
86 #define MOTION_MODE_SEEK 0 // G0
87 #define MOTION_MODE_LINEAR 1 // G1
88 #define MOTION_MODE_CW_ARC 2 // G2
89 #define MOTION_MODE_CCW_ARC 3 // G3
90 #define MOTION_MODE_CANCEL 4 // G80
91
92 #define PATH_CONTROL_MODE_EXACT_PATH 0
93 #define PATH_CONTROL_MODE_EXACT_STOP 1
94 #define PATH_CONTROL_MODE_CONTINOUS 2
95
96 #define PROGRAM_FLOW_RUNNING 0
97 #define PROGRAM_FLOW_PAUSED 1
98 #define PROGRAM_FLOW_COMPLETED 2
99
100 #define SPINDLE_DIRECTION_CW 0
101 #define SPINDLE_DIRECTION_CCW 1
102
103 #define ARC_ANGULAR_TRAVEL_EPSILON 5E-7 // Float (radians)
104
105 // 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
106 // It takes care of cutting arcs into segments, same thing for line that are too long
107
108 Robot::Robot()
109 {
110 this->inch_mode = false;
111 this->absolute_mode = true;
112 this->motion_mode = MOTION_MODE_SEEK;
113 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
114 clear_vector(this->last_milestone);
115 clear_vector(this->last_machine_position);
116 this->arm_solution = NULL;
117 seconds_per_minute = 60.0F;
118 this->clearToolOffset();
119 this->compensationTransform = nullptr;
120 this->wcs_offsets.fill(wcs_t(0.0F, 0.0F, 0.0F));
121 this->g92_offset = wcs_t(0.0F, 0.0F, 0.0F);
122 this->next_command_is_MCS = false;
123 }
124
125 //Called when the module has just been loaded
126 void Robot::on_module_loaded()
127 {
128 this->register_for_event(ON_GCODE_RECEIVED);
129
130 // Configuration
131 this->load_config();
132 }
133
134 #define ACTUATOR_CHECKSUMS(X) { \
135 CHECKSUM(X "_step_pin"), \
136 CHECKSUM(X "_dir_pin"), \
137 CHECKSUM(X "_en_pin"), \
138 CHECKSUM(X "_steps_per_mm"), \
139 CHECKSUM(X "_max_rate") \
140 }
141
142 void Robot::load_config()
143 {
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.
147 // Here we read the config to find out which arm solution to use
148 if (this->arm_solution) delete this->arm_solution;
149 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
150 // Note checksums are not const expressions when in debug mode, so don't use switch
151 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
152 this->arm_solution = new HBotSolution(THEKERNEL->config);
153
154 } else if(solution_checksum == corexz_checksum) {
155 this->arm_solution = new CoreXZSolution(THEKERNEL->config);
156
157 } else if(solution_checksum == rostock_checksum || solution_checksum == kossel_checksum || solution_checksum == delta_checksum || solution_checksum == linear_delta_checksum) {
158 this->arm_solution = new LinearDeltaSolution(THEKERNEL->config);
159
160 } else if(solution_checksum == rotatable_cartesian_checksum) {
161 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
162
163 } else if(solution_checksum == rotary_delta_checksum) {
164 this->arm_solution = new RotaryDeltaSolution(THEKERNEL->config);
165
166 } else if(solution_checksum == morgan_checksum) {
167 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
168
169 } else if(solution_checksum == cartesian_checksum) {
170 this->arm_solution = new CartesianSolution(THEKERNEL->config);
171
172 } else {
173 this->arm_solution = new CartesianSolution(THEKERNEL->config);
174 }
175
176 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
177 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
178 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
179 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
180 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.5f)->as_number();
181 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
182
183 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
184 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
185 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
186
187 // Make our 3 StepperMotors
188 uint16_t const checksums[][5] = {
189 ACTUATOR_CHECKSUMS("alpha"),
190 ACTUATOR_CHECKSUMS("beta"),
191 ACTUATOR_CHECKSUMS("gamma"),
192 #if MAX_ROBOT_ACTUATORS > 3
193 ACTUATOR_CHECKSUMS("delta"),
194 ACTUATOR_CHECKSUMS("epsilon"),
195 ACTUATOR_CHECKSUMS("zeta")
196 #endif
197 };
198 constexpr size_t actuator_checksum_count = sizeof(checksums) / sizeof(checksums[0]);
199 static_assert(actuator_checksum_count >= k_max_actuators, "Robot checksum array too small for k_max_actuators");
200
201 size_t motor_count = std::min(this->arm_solution->get_actuator_count(), k_max_actuators);
202 for (size_t a = 0; a < motor_count; a++) {
203 Pin pins[3]; //step, dir, enable
204 for (size_t i = 0; i < 3; i++) {
205 pins[i].from_string(THEKERNEL->config->value(checksums[a][i])->by_default("nc")->as_string())->as_output();
206 }
207 actuators[a] = new StepperMotor(pins[0], pins[1], pins[2]);
208
209 actuators[a]->change_steps_per_mm(THEKERNEL->config->value(checksums[a][3])->by_default(a == 2 ? 2560.0F : 80.0F)->as_number());
210 actuators[a]->set_max_rate(THEKERNEL->config->value(checksums[a][4])->by_default(30000.0F)->as_number());
211 }
212
213 check_max_actuator_speeds(); // check the configs are sane
214
215 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
216 // so the first move can be correct if homing is not performed
217 ActuatorCoordinates actuator_pos;
218 arm_solution->cartesian_to_actuator(last_milestone, actuator_pos);
219 for (size_t i = 0; i < actuators.size(); i++)
220 actuators[i]->change_last_milestone(actuator_pos[i]);
221
222 //this->clearToolOffset();
223 }
224
225 void Robot::push_state()
226 {
227 bool am = this->absolute_mode;
228 bool im = this->inch_mode;
229 saved_state_t s(this->feed_rate, this->seek_rate, am, im, current_wcs);
230 state_stack.push(s);
231 }
232
233 void Robot::pop_state()
234 {
235 if(!state_stack.empty()) {
236 auto s = state_stack.top();
237 state_stack.pop();
238 this->feed_rate = std::get<0>(s);
239 this->seek_rate = std::get<1>(s);
240 this->absolute_mode = std::get<2>(s);
241 this->inch_mode = std::get<3>(s);
242 this->current_wcs = std::get<4>(s);
243 }
244 }
245
246 std::vector<Robot::wcs_t> Robot::get_wcs_state() const
247 {
248 std::vector<wcs_t> v;
249 v.push_back(wcs_t(current_wcs, MAX_WCS, 0));
250 for(auto& i : wcs_offsets) {
251 v.push_back(i);
252 }
253 v.push_back(g92_offset);
254 v.push_back(tool_offset);
255 return v;
256 }
257
258 int Robot::print_position(uint8_t subcode, char *buf, size_t bufsize) const
259 {
260 // M114.1 is a new way to do this (similar to how GRBL does it).
261 // it returns the realtime position based on the current step position of the actuators.
262 // this does require a FK to get a machine position from the actuator position
263 // and then invert all the transforms to get a workspace position from machine position
264 // M114 just does it the old way uses last_milestone and does inversse tranfroms to get the requested position
265 int n = 0;
266 if(subcode == 0) { // M114 print WCS
267 wcs_t pos= mcs2wcs(last_milestone);
268 n = snprintf(buf, bufsize, "C: X:%1.3f Y:%1.3f Z:%1.3f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
269
270 } else if(subcode == 4) { // M114.3 print last milestone (which should be the same as machine position if axis are not moving and no level compensation)
271 n = snprintf(buf, bufsize, "LMS: X:%1.3f Y:%1.3f Z:%1.3f", last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
272
273 } else if(subcode == 5) { // M114.4 print last machine position (which should be the same as M114.1 if axis are not moving and no level compensation)
274 n = snprintf(buf, bufsize, "LMCS: X:%1.3f Y:%1.3f Z:%1.3f", last_machine_position[X_AXIS], last_machine_position[Y_AXIS], last_machine_position[Z_AXIS]);
275
276 } else {
277 // get real time positions
278 // current actuator position in mm
279 ActuatorCoordinates current_position{
280 actuators[X_AXIS]->get_current_position(),
281 actuators[Y_AXIS]->get_current_position(),
282 actuators[Z_AXIS]->get_current_position()
283 };
284
285 // get machine position from the actuator position using FK
286 float mpos[3];
287 arm_solution->actuator_to_cartesian(current_position, mpos);
288
289 if(subcode == 1) { // M114.1 print realtime WCS
290 // FIXME this currently includes the compensation transform which is incorrect so will be slightly off if it is in effect (but by very little)
291 wcs_t pos= mcs2wcs(mpos);
292 n = snprintf(buf, bufsize, "C: X:%1.3f Y:%1.3f Z:%1.3f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
293
294 } else if(subcode == 2) { // M114.1 print realtime Machine coordinate system
295 n = snprintf(buf, bufsize, "MPOS: X:%1.3f Y:%1.3f Z:%1.3f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
296
297 } else if(subcode == 3) { // M114.2 print realtime actuator position
298 n = snprintf(buf, bufsize, "APOS: A:%1.3f B:%1.3f C:%1.3f", current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
299 }
300 }
301 return n;
302 }
303
304 // converts current last milestone (machine position without compensation transform) to work coordinate system (inverse transform)
305 Robot::wcs_t Robot::mcs2wcs(const float *pos) const
306 {
307 return std::make_tuple(
308 pos[X_AXIS] - std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset) - std::get<X_AXIS>(tool_offset),
309 pos[Y_AXIS] - std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset) - std::get<Y_AXIS>(tool_offset),
310 pos[Z_AXIS] - std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset) - std::get<Z_AXIS>(tool_offset)
311 );
312 }
313
314 // this does a sanity check that actuator speeds do not exceed steps rate capability
315 // we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
316 void Robot::check_max_actuator_speeds()
317 {
318 for (size_t i = 0; i < actuators.size(); i++) {
319 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
320 if (step_freq > THEKERNEL->base_stepping_frequency) {
321 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
322 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);
323 }
324 }
325 }
326
327 //A GCode has been received
328 //See if the current Gcode line has some orders for us
329 void Robot::on_gcode_received(void *argument)
330 {
331 Gcode *gcode = static_cast<Gcode *>(argument);
332
333 this->motion_mode = -1;
334
335 if( gcode->has_g) {
336 switch( gcode->g ) {
337 case 0: this->motion_mode = MOTION_MODE_SEEK; break;
338 case 1: this->motion_mode = MOTION_MODE_LINEAR; break;
339 case 2: this->motion_mode = MOTION_MODE_CW_ARC; break;
340 case 3: this->motion_mode = MOTION_MODE_CCW_ARC; break;
341 case 4: { // G4 pause
342 uint32_t delay_ms = 0;
343 if (gcode->has_letter('P')) {
344 delay_ms = gcode->get_int('P');
345 }
346 if (gcode->has_letter('S')) {
347 delay_ms += gcode->get_int('S') * 1000;
348 }
349 if (delay_ms > 0) {
350 // drain queue
351 THEKERNEL->conveyor->wait_for_empty_queue();
352 // wait for specified time
353 uint32_t start = us_ticker_read(); // mbed call
354 while ((us_ticker_read() - start) < delay_ms * 1000) {
355 THEKERNEL->call_event(ON_IDLE, this);
356 if(THEKERNEL->is_halted()) return;
357 }
358 }
359 }
360 break;
361
362 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
363 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
364 size_t n = gcode->get_uint('P');
365 if(n == 0) n = current_wcs; // set current coordinate system
366 else --n;
367 if(n < MAX_WCS) {
368 float x, y, z;
369 std::tie(x, y, z) = wcs_offsets[n];
370 if(gcode->get_int('L') == 20) {
371 // this makes the current machine position (less compensation transform) the offset
372 // get current position in WCS
373 wcs_t pos= mcs2wcs(last_milestone);
374
375 if(gcode->has_letter('X')){
376 x -= to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
377 }
378
379 if(gcode->has_letter('Y')){
380 y -= to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
381 }
382 if(gcode->has_letter('Z')) {
383 z -= to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
384 }
385
386 } else {
387 // the value is the offset from machine zero
388 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
389 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
390 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
391 }
392 wcs_offsets[n] = wcs_t(x, y, z);
393 }
394 }
395 break;
396
397 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
398 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
399 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
400 case 20: this->inch_mode = true; break;
401 case 21: this->inch_mode = false; break;
402
403 case 54: case 55: case 56: case 57: case 58: case 59:
404 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
405 current_wcs = gcode->g - 54;
406 if(gcode->g == 59 && gcode->subcode > 0) {
407 current_wcs += gcode->subcode;
408 if(current_wcs >= MAX_WCS) current_wcs = MAX_WCS - 1;
409 }
410 break;
411
412 case 90: this->absolute_mode = true; break;
413 case 91: this->absolute_mode = false; break;
414
415 case 92: {
416 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
417 // reset G92 offsets to 0
418 g92_offset = wcs_t(0, 0, 0);
419
420 } else {
421 // standard setting of the g92 offsets, making current WCS position whatever the coordinate arguments are
422 float x, y, z;
423 std::tie(x, y, z) = g92_offset;
424 // get current position in WCS
425 wcs_t pos= mcs2wcs(last_milestone);
426
427 // adjust g92 offset to make the current wpos == the value requested
428 if(gcode->has_letter('X')){
429 x += to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
430 }
431 if(gcode->has_letter('Y')){
432 y += to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
433 }
434 if(gcode->has_letter('Z')) {
435 z += to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
436 }
437 g92_offset = wcs_t(x, y, z);
438 }
439
440 return;
441 }
442 }
443
444 } else if( gcode->has_m) {
445 switch( gcode->m ) {
446 case 2: // M2 end of program
447 case 30: // M30 end of program
448 current_wcs = 0;
449 absolute_mode = true;
450 break;
451
452 case 92: // M92 - set steps per mm
453 if (gcode->has_letter('X'))
454 actuators[0]->change_steps_per_mm(this->to_millimeters(gcode->get_value('X')));
455 if (gcode->has_letter('Y'))
456 actuators[1]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Y')));
457 if (gcode->has_letter('Z'))
458 actuators[2]->change_steps_per_mm(this->to_millimeters(gcode->get_value('Z')));
459
460 gcode->stream->printf("X:%f Y:%f Z:%f ", actuators[0]->steps_per_mm, actuators[1]->steps_per_mm, actuators[2]->steps_per_mm);
461 gcode->add_nl = true;
462 check_max_actuator_speeds();
463 return;
464
465 case 114:{
466 char buf[64];
467 int n= print_position(gcode->subcode, buf, sizeof buf);
468 if(n > 0) gcode->txt_after_ok.append(buf, n);
469 return;
470 }
471
472 case 120: // push state
473 push_state();
474 break;
475
476 case 121: // pop state
477 pop_state();
478 break;
479
480 case 203: // M203 Set maximum feedrates in mm/sec
481 if (gcode->has_letter('X'))
482 this->max_speeds[X_AXIS] = gcode->get_value('X');
483 if (gcode->has_letter('Y'))
484 this->max_speeds[Y_AXIS] = gcode->get_value('Y');
485 if (gcode->has_letter('Z'))
486 this->max_speeds[Z_AXIS] = gcode->get_value('Z');
487 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
488 if (gcode->has_letter('A' + i))
489 actuators[i]->set_max_rate(gcode->get_value('A' + i));
490 }
491 check_max_actuator_speeds();
492
493 if(gcode->get_num_args() == 0) {
494 gcode->stream->printf("X:%g Y:%g Z:%g",
495 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
496 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
497 gcode->stream->printf(" %c : %g", 'A' + i, actuators[i]->get_max_rate()); //xxx
498 }
499 gcode->add_nl = true;
500 }
501 break;
502
503 case 204: // M204 Snnn - set acceleration to nnn, Znnn sets z acceleration
504 if (gcode->has_letter('S')) {
505 float acc = gcode->get_value('S'); // mm/s^2
506 // enforce minimum
507 if (acc < 1.0F)
508 acc = 1.0F;
509 THEKERNEL->planner->acceleration = acc;
510 }
511 if (gcode->has_letter('Z')) {
512 float acc = gcode->get_value('Z'); // mm/s^2
513 // enforce positive
514 if (acc < 0.0F)
515 acc = 0.0F;
516 THEKERNEL->planner->z_acceleration = acc;
517 }
518 break;
519
520 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed, Ynnn - set minimum step rate
521 if (gcode->has_letter('X')) {
522 float jd = gcode->get_value('X');
523 // enforce minimum
524 if (jd < 0.0F)
525 jd = 0.0F;
526 THEKERNEL->planner->junction_deviation = jd;
527 }
528 if (gcode->has_letter('Z')) {
529 float jd = gcode->get_value('Z');
530 // enforce minimum, -1 disables it and uses regular junction deviation
531 if (jd < -1.0F)
532 jd = -1.0F;
533 THEKERNEL->planner->z_junction_deviation = jd;
534 }
535 if (gcode->has_letter('S')) {
536 float mps = gcode->get_value('S');
537 // enforce minimum
538 if (mps < 0.0F)
539 mps = 0.0F;
540 THEKERNEL->planner->minimum_planner_speed = mps;
541 }
542 if (gcode->has_letter('Y')) {
543 actuators[0]->default_minimum_actuator_rate = gcode->get_value('Y');
544 }
545 break;
546
547 case 220: // M220 - speed override percentage
548 if (gcode->has_letter('S')) {
549 float factor = gcode->get_value('S');
550 // enforce minimum 10% speed
551 if (factor < 10.0F)
552 factor = 10.0F;
553 // enforce maximum 10x speed
554 if (factor > 1000.0F)
555 factor = 1000.0F;
556
557 seconds_per_minute = 6000.0F / factor;
558 } else {
559 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
560 }
561 break;
562
563 case 400: // wait until all moves are done up to this point
564 THEKERNEL->conveyor->wait_for_empty_queue();
565 break;
566
567 case 500: // M500 saves some volatile settings to config override file
568 case 503: { // M503 just prints the settings
569 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);
570 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f Z%1.5f\n", THEKERNEL->planner->acceleration, THEKERNEL->planner->z_acceleration);
571 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);
572 gcode->stream->printf(";Max feedrates in mm/sec, XYZ cartesian, ABC actuator:\nM203 X%1.5f Y%1.5f Z%1.5f",
573 this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS]);
574 for (size_t i = 0; i < 3 && i < actuators.size(); i++) {
575 gcode->stream->printf(" %c%1.5f", 'A' + i, actuators[i]->get_max_rate());
576 }
577 gcode->stream->printf("\n");
578
579 // get or save any arm solution specific optional values
580 BaseSolution::arm_options_t options;
581 if(arm_solution->get_optional(options) && !options.empty()) {
582 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
583 for(auto &i : options) {
584 gcode->stream->printf(" %c%1.4f", i.first, i.second);
585 }
586 gcode->stream->printf("\n");
587 }
588
589 // save wcs_offsets and current_wcs
590 // TODO this may need to be done whenever they change to be compliant
591 gcode->stream->printf(";WCS settings\n");
592 gcode->stream->printf("%s\n", wcs2gcode(current_wcs).c_str());
593 int n = 1;
594 for(auto &i : wcs_offsets) {
595 if(i != wcs_t(0, 0, 0)) {
596 float x, y, z;
597 std::tie(x, y, z) = i;
598 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f ; %s\n", n, x, y, z, wcs2gcode(n-1).c_str());
599 }
600 ++n;
601 }
602 }
603
604 if(gcode->m == 503) {
605 // just print the G92 setting as it is not saved
606 // TODO linuxcnc does seem to save G92, so maybe we should here too
607 if(g92_offset != wcs_t(0, 0, 0)) {
608 float x, y, z;
609 std::tie(x, y, z) = g92_offset;
610 gcode->stream->printf("G92 X%f Y%f Z%f ; NOT SAVED\n", x, y, z);
611 }
612 }
613 break;
614
615 case 665: { // M665 set optional arm solution variables based on arm solution.
616 // the parameter args could be any letter each arm solution only accepts certain ones
617 BaseSolution::arm_options_t options = gcode->get_args();
618 options.erase('S'); // don't include the S
619 options.erase('U'); // don't include the U
620 if(options.size() > 0) {
621 // set the specified options
622 arm_solution->set_optional(options);
623 }
624 options.clear();
625 if(arm_solution->get_optional(options)) {
626 // foreach optional value
627 for(auto &i : options) {
628 // print all current values of supported options
629 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
630 gcode->add_nl = true;
631 }
632 }
633
634 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
635 this->delta_segments_per_second = gcode->get_value('S');
636 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
637
638 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
639 this->mm_per_line_segment = gcode->get_value('U');
640 this->delta_segments_per_second = 0;
641 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
642 }
643
644 break;
645 }
646 }
647 }
648
649 if( this->motion_mode >= 0) {
650 process_move(gcode);
651 }
652
653 next_command_is_MCS = false; // must be on same line as G0 or G1
654 }
655
656 // process a G0/G1/G2/G3
657 void Robot::process_move(Gcode *gcode)
658 {
659 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
660 float param[3]{NAN, NAN, NAN};
661 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
662 char letter= 'X'+i;
663 if( gcode->has_letter(letter) ) {
664 param[i] = this->to_millimeters(gcode->get_value(letter));
665 }
666 }
667
668 float offset[3]{0,0,0};
669 for(char letter = 'I'; letter <= 'K'; letter++) {
670 if( gcode->has_letter(letter) ) {
671 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
672 }
673 }
674
675 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
676 float target[3]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
677 if(!next_command_is_MCS) {
678 if(this->absolute_mode) {
679 // apply wcs offsets and g92 offset and tool offset
680 if(!isnan(param[X_AXIS])) {
681 target[X_AXIS]= param[X_AXIS] + std::get<X_AXIS>(wcs_offsets[current_wcs]) - std::get<X_AXIS>(g92_offset) + std::get<X_AXIS>(tool_offset);
682 }
683
684 if(!isnan(param[Y_AXIS])) {
685 target[Y_AXIS]= param[Y_AXIS] + std::get<Y_AXIS>(wcs_offsets[current_wcs]) - std::get<Y_AXIS>(g92_offset) + std::get<Y_AXIS>(tool_offset);
686 }
687
688 if(!isnan(param[Z_AXIS])) {
689 target[Z_AXIS]= param[Z_AXIS] + std::get<Z_AXIS>(wcs_offsets[current_wcs]) - std::get<Z_AXIS>(g92_offset) + std::get<Z_AXIS>(tool_offset);
690 }
691
692 }else{
693 // they are deltas from the last_milestone if specified
694 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
695 if(!isnan(param[i])) target[i] = param[i] + last_milestone[i];
696 }
697 }
698
699 }else{
700 // already in machine coordinates, we do not add tool offset for that
701 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
702 if(!isnan(param[i])) target[i] = param[i];
703 }
704 }
705
706 if( gcode->has_letter('F') ) {
707 if( this->motion_mode == MOTION_MODE_SEEK )
708 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
709 else
710 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
711 }
712
713 bool moved= false;
714 //Perform any physical actions
715 switch(this->motion_mode) {
716 case MOTION_MODE_CANCEL:
717 break;
718 case MOTION_MODE_SEEK:
719 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute );
720 break;
721 case MOTION_MODE_LINEAR:
722 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute );
723 break;
724 case MOTION_MODE_CW_ARC:
725 case MOTION_MODE_CCW_ARC:
726 moved= this->compute_arc(gcode, offset, target );
727 break;
728 }
729
730 if(moved) {
731 // set last_milestone to the calculated target
732 memcpy(this->last_milestone, target, sizeof(this->last_milestone));
733 }
734 }
735
736 // We received a new gcode, and one of the functions
737 // determined the distance for that given gcode. So now we can attach this gcode to the right block
738 // and continue
739 void Robot::distance_in_gcode_is_known(Gcode * gcode)
740 {
741 //If the queue is empty, execute immediatly, otherwise attach to the last added block
742 THEKERNEL->conveyor->append_gcode(gcode);
743 }
744
745 // reset the machine position for all axis. Used for homing.
746 // During homing compensation is turned off (actually not used as it drives steppers directly)
747 // once homed and reset_axis called compensation is used for the move to origin and back off home if enabled,
748 // so in those cases the final position is compensated.
749 void Robot::reset_axis_position(float x, float y, float z)
750 {
751 // these are set to the same as compensation was not used to get to the current position
752 last_machine_position[X_AXIS]= last_milestone[X_AXIS] = x;
753 last_machine_position[Y_AXIS]= last_milestone[Y_AXIS] = y;
754 last_machine_position[Z_AXIS]= last_milestone[Z_AXIS] = z;
755
756 // now set the actuator positions to match
757 ActuatorCoordinates actuator_pos;
758 arm_solution->cartesian_to_actuator(this->last_machine_position, actuator_pos);
759 for (size_t i = 0; i < actuators.size(); i++)
760 actuators[i]->change_last_milestone(actuator_pos[i]);
761 }
762
763 // Reset the position for an axis (used in homing)
764 void Robot::reset_axis_position(float position, int axis)
765 {
766 last_milestone[axis] = position;
767 reset_axis_position(last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]);
768 }
769
770 // similar to reset_axis_position but directly sets the actuator positions in actuators units (eg mm for cartesian, degrees for rotary delta)
771 // then sets the axis postions to match. currently only called from G28.4 and M306 in Endstops.cpp
772 void Robot::reset_actuator_position(float a, float b, float c)
773 {
774 // NOTE this does NOT support the multiple actuator HACK. so if there are more than 3 actuators this will probably not work
775 if(!isnan(a)) actuators[0]->change_last_milestone(a);
776 if(!isnan(b)) actuators[1]->change_last_milestone(b);
777 if(!isnan(c)) actuators[2]->change_last_milestone(c);
778
779 // now correct axis positions then recorrect actuator to account for rounding
780 reset_position_from_current_actuator_position();
781 }
782
783 // Use FK to find out where actuator is and reset to match
784 void Robot::reset_position_from_current_actuator_position()
785 {
786 ActuatorCoordinates actuator_pos;
787 for (size_t i = 0; i < actuators.size(); i++) {
788 // NOTE actuator::current_position is curently NOT the same as actuator::last_milestone after an abrupt abort
789 actuator_pos[i] = actuators[i]->get_current_position();
790 }
791
792 // discover machine position from where actuators actually are
793 arm_solution->actuator_to_cartesian(actuator_pos, last_machine_position);
794 // FIXME problem is this includes any compensation transform, and without an inverse compensation we cannot get a correct last_milestone
795 memcpy(last_milestone, last_machine_position, sizeof last_milestone);
796
797 // now reset actuator::last_milestone, NOTE this may lose a little precision as FK is not always entirely accurate.
798 // NOTE This is required to sync the machine position with the actuator position, we do a somewhat redundant cartesian_to_actuator() call
799 // to get everything in perfect sync.
800 arm_solution->cartesian_to_actuator(last_machine_position, actuator_pos);
801 for (size_t i = 0; i < actuators.size(); i++)
802 actuators[i]->change_last_milestone(actuator_pos[i]);
803 }
804
805 // Convert target (in machine coordinates) from millimeters to steps, and append this to the planner
806 // target is in machine coordinates without the compensation transform, however we save a last_machine_position that includes
807 // all transforms and is what we actually convert to actuator positions
808 bool Robot::append_milestone(Gcode * gcode, const float target[], float rate_mm_s)
809 {
810 float deltas[3];
811 float unit_vec[3];
812 ActuatorCoordinates actuator_pos;
813 float transformed_target[3]; // adjust target for bed compensation and WCS offsets
814 float millimeters_of_travel;
815
816 // unity transform by default
817 memcpy(transformed_target, target, sizeof(transformed_target));
818
819 // check function pointer and call if set to transform the target to compensate for bed
820 if(compensationTransform) {
821 // some compensation strategies can transform XYZ, some just change Z
822 compensationTransform(transformed_target);
823 }
824
825 // find distance moved by each axis, use transformed target from the current machine position
826 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
827 deltas[axis] = transformed_target[axis] - last_machine_position[axis];
828 }
829
830 // Compute how long this move moves, so we can attach it to the block for later use
831 millimeters_of_travel = sqrtf( powf( deltas[X_AXIS], 2 ) + powf( deltas[Y_AXIS], 2 ) + powf( deltas[Z_AXIS], 2 ) );
832
833 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
834 // as the last milestone won't be updated we do not actually lose any moves as they will be accounted for in the next move
835 if(millimeters_of_travel < 0.00001F) return false;
836
837 // this is the machine position
838 memcpy(this->last_machine_position, transformed_target, sizeof(this->last_machine_position));
839
840 // find distance unit vector
841 for (int i = 0; i < 3; i++)
842 unit_vec[i] = deltas[i] / millimeters_of_travel;
843
844 // Do not move faster than the configured cartesian limits
845 for (int axis = X_AXIS; axis <= Z_AXIS; axis++) {
846 if ( max_speeds[axis] > 0 ) {
847 float axis_speed = fabs(unit_vec[axis] * rate_mm_s);
848
849 if (axis_speed > max_speeds[axis])
850 rate_mm_s *= ( max_speeds[axis] / axis_speed );
851 }
852 }
853
854 // find actuator position given the machine position, use actual adjusted target
855 arm_solution->cartesian_to_actuator( this->last_machine_position, actuator_pos );
856
857 float isecs = rate_mm_s / millimeters_of_travel;
858 // check per-actuator speed limits
859 for (size_t actuator = 0; actuator < actuators.size(); actuator++) {
860 float actuator_rate = fabsf(actuator_pos[actuator] - actuators[actuator]->last_milestone_mm) * isecs;
861 if (actuator_rate > actuators[actuator]->get_max_rate()) {
862 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
863 isecs = rate_mm_s / millimeters_of_travel;
864 }
865 }
866
867 // Append the block to the planner
868 THEKERNEL->planner->append_block( actuator_pos, rate_mm_s, millimeters_of_travel, unit_vec );
869
870 return true;
871 }
872
873 // Append a move to the queue ( cutting it into segments if needed )
874 bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s )
875 {
876 // Find out the distance for this move in MCS
877 // 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
878 gcode->millimeters_of_travel = sqrtf(powf( target[X_AXIS] - last_milestone[X_AXIS], 2 ) + powf( target[Y_AXIS] - last_milestone[Y_AXIS], 2 ) + powf( target[Z_AXIS] - last_milestone[Z_AXIS], 2 ));
879
880 // We ignore non- XYZ moves ( for example, extruder moves are not XYZ moves )
881 if( gcode->millimeters_of_travel < 0.00001F ) return false;
882
883 // Mark the gcode as having a known distance
884 this->distance_in_gcode_is_known( gcode );
885
886 // if we have volumetric limits enabled we calculate the volume for this move and limit the rate if it exceeds the stated limit
887 // Note we need to be using volumetric extrusion for this to work as Ennn is in mm³ not mm
888 // We also check we are not exceeding the E max_speed for the current extruder
889 // We ask Extruder to do all the work, but as Extruder won't even see this gcode until after it has been planned
890 // we need to ask it now passing in the relevant data.
891 // NOTE we need to do this before we segment the line (for deltas)
892 if(gcode->has_letter('E')) {
893 float data[2];
894 data[0] = gcode->get_value('E'); // E target (maybe absolute or relative)
895 data[1] = rate_mm_s / gcode->millimeters_of_travel; // inverted seconds for the move
896 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
897 rate_mm_s *= data[1];
898 //THEKERNEL->streams->printf("Extruder has changed the rate by %f to %f\n", data[1], rate_mm_s);
899 }
900 }
901
902 // We cut the line into smaller segments. This is only needed on a cartesian robot for zgrid, but always necessary for robots with rotational axes like Deltas.
903 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
904 // 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
905 uint16_t segments;
906
907 if(this->delta_segments_per_second > 1.0F) {
908 // enabled if set to something > 1, it is set to 0.0 by default
909 // segment based on current speed and requested segments per second
910 // the faster the travel speed the fewer segments needed
911 // NOTE rate is mm/sec and we take into account any speed override
912 float seconds = gcode->millimeters_of_travel / rate_mm_s;
913 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
914 // TODO if we are only moving in Z on a delta we don't really need to segment at all
915
916 } else {
917 if(this->mm_per_line_segment == 0.0F) {
918 segments = 1; // don't split it up
919 } else {
920 segments = ceilf( gcode->millimeters_of_travel / this->mm_per_line_segment);
921 }
922 }
923
924 bool moved= false;
925 if (segments > 1) {
926 // A vector to keep track of the endpoint of each segment
927 float segment_delta[3];
928 float segment_end[3]{last_milestone[X_AXIS], last_milestone[Y_AXIS], last_milestone[Z_AXIS]};
929
930 // How far do we move each segment?
931 for (int i = X_AXIS; i <= Z_AXIS; i++)
932 segment_delta[i] = (target[i] - last_milestone[i]) / segments;
933
934 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
935 // We always add another point after this loop so we stop at segments-1, ie i < segments
936 for (int i = 1; i < segments; i++) {
937 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
938 for(int axis = X_AXIS; axis <= Z_AXIS; axis++ )
939 segment_end[axis] += segment_delta[axis];
940
941 // Append the end of this segment to the queue
942 bool b= this->append_milestone(gcode, segment_end, rate_mm_s);
943 moved= moved || b;
944 }
945 }
946
947 // Append the end of this full move to the queue
948 if(this->append_milestone(gcode, target, rate_mm_s)) moved= true;
949
950 this->next_command_is_MCS = false; // always reset this
951
952 if(moved) {
953 // if adding these blocks didn't start executing, do that now
954 THEKERNEL->conveyor->ensure_running();
955 }
956
957 return moved;
958 }
959
960
961 // Append an arc to the queue ( cutting it into segments as needed )
962 bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
963 {
964
965 // Scary math
966 float center_axis0 = this->last_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
967 float center_axis1 = this->last_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
968 float linear_travel = target[this->plane_axis_2] - this->last_milestone[this->plane_axis_2];
969 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to current location
970 float r_axis1 = -offset[this->plane_axis_1];
971 float rt_axis0 = target[this->plane_axis_0] - center_axis0;
972 float rt_axis1 = target[this->plane_axis_1] - center_axis1;
973
974 // Patch from GRBL Firmware - Christoph Baumann 04072015
975 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
976 float angular_travel = atan2(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
977 if (is_clockwise) { // Correct atan2 output per direction
978 if (angular_travel >= -ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel -= 2 * M_PI; }
979 } else {
980 if (angular_travel <= ARC_ANGULAR_TRAVEL_EPSILON) { angular_travel += 2 * M_PI; }
981 }
982
983 // Find the distance for this gcode
984 gcode->millimeters_of_travel = hypotf(angular_travel * radius, fabs(linear_travel));
985
986 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
987 if( gcode->millimeters_of_travel < 0.00001F ) {
988 return false;
989 }
990
991 // Mark the gcode as having a known distance
992 this->distance_in_gcode_is_known( gcode );
993
994 // Figure out how many segments for this gcode
995 uint16_t segments = floorf(gcode->millimeters_of_travel / this->mm_per_arc_segment);
996
997 float theta_per_segment = angular_travel / segments;
998 float linear_per_segment = linear_travel / segments;
999
1000 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
1001 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
1002 r_T = [cos(phi) -sin(phi);
1003 sin(phi) cos(phi] * r ;
1004 For arc generation, the center of the circle is the axis of rotation and the radius vector is
1005 defined from the circle center to the initial position. Each line segment is formed by successive
1006 vector rotations. This requires only two cos() and sin() computations to form the rotation
1007 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1008 all float numbers are single precision on the Arduino. (True float precision will not have
1009 round off issues for CNC applications.) Single precision error can accumulate to be greater than
1010 tool precision in some cases. Therefore, arc path correction is implemented.
1011
1012 Small angle approximation may be used to reduce computation overhead further. This approximation
1013 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
1014 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
1015 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
1016 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
1017 issue for CNC machines with the single precision Arduino calculations.
1018 This approximation also allows mc_arc to immediately insert a line segment into the planner
1019 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
1020 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
1021 This is important when there are successive arc motions.
1022 */
1023 // Vector rotation matrix values
1024 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1025 float sin_T = theta_per_segment;
1026
1027 float arc_target[3];
1028 float sin_Ti;
1029 float cos_Ti;
1030 float r_axisi;
1031 uint16_t i;
1032 int8_t count = 0;
1033
1034 // Initialize the linear axis
1035 arc_target[this->plane_axis_2] = this->last_milestone[this->plane_axis_2];
1036
1037 bool moved= false;
1038 for (i = 1; i < segments; i++) { // Increment (segments-1)
1039 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
1040
1041 if (count < this->arc_correction ) {
1042 // Apply vector rotation matrix
1043 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
1044 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
1045 r_axis1 = r_axisi;
1046 count++;
1047 } else {
1048 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
1049 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
1050 cos_Ti = cosf(i * theta_per_segment);
1051 sin_Ti = sinf(i * theta_per_segment);
1052 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1053 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1054 count = 0;
1055 }
1056
1057 // Update arc_target location
1058 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1059 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1060 arc_target[this->plane_axis_2] += linear_per_segment;
1061
1062 // Append this segment to the queue
1063 bool b= this->append_milestone(gcode, arc_target, this->feed_rate / seconds_per_minute);
1064 moved= moved || b;
1065 }
1066
1067 // Ensure last segment arrives at target location.
1068 if(this->append_milestone(gcode, target, this->feed_rate / seconds_per_minute)) moved= true;
1069
1070 return moved;
1071 }
1072
1073 // Do the math for an arc and add it to the queue
1074 bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[])
1075 {
1076
1077 // Find the radius
1078 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
1079
1080 // Set clockwise/counter-clockwise sign for mc_arc computations
1081 bool is_clockwise = false;
1082 if( this->motion_mode == MOTION_MODE_CW_ARC ) {
1083 is_clockwise = true;
1084 }
1085
1086 // Append arc
1087 return this->append_arc(gcode, target, offset, radius, is_clockwise );
1088 }
1089
1090
1091 float Robot::theta(float x, float y)
1092 {
1093 float t = atanf(x / fabs(y));
1094 if (y > 0) {
1095 return(t);
1096 } else {
1097 if (t > 0) {
1098 return(M_PI - t);
1099 } else {
1100 return(-M_PI - t);
1101 }
1102 }
1103 }
1104
1105 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1106 {
1107 this->plane_axis_0 = axis_0;
1108 this->plane_axis_1 = axis_1;
1109 this->plane_axis_2 = axis_2;
1110 }
1111
1112 void Robot::clearToolOffset()
1113 {
1114 this->tool_offset= wcs_t(0,0,0);
1115 }
1116
1117 void Robot::setToolOffset(const float offset[3])
1118 {
1119 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
1120 }
1121
1122 float Robot::get_feed_rate() const
1123 {
1124 return THEKERNEL->gcode_dispatch->get_modal_command() == 0 ? seek_rate : feed_rate;
1125 }