e193e368009426b06ce8197f2151af6c6bb8bc94
[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 "Robot.h"
12 #include "Planner.h"
13 #include "Conveyor.h"
14 #include "Pin.h"
15 #include "StepperMotor.h"
16 #include "Gcode.h"
17 #include "PublicDataRequest.h"
18 #include "PublicData.h"
19 #include "arm_solutions/BaseSolution.h"
20 #include "arm_solutions/CartesianSolution.h"
21 #include "arm_solutions/RotatableCartesianSolution.h"
22 #include "arm_solutions/LinearDeltaSolution.h"
23 #include "arm_solutions/RotaryDeltaSolution.h"
24 #include "arm_solutions/HBotSolution.h"
25 #include "arm_solutions/CoreXZSolution.h"
26 #include "arm_solutions/MorganSCARASolution.h"
27 #include "StepTicker.h"
28 #include "checksumm.h"
29 #include "utils.h"
30 #include "ConfigValue.h"
31 #include "libs/StreamOutput.h"
32 #include "StreamOutputPool.h"
33 #include "ExtruderPublicAccess.h"
34 #include "GcodeDispatch.h"
35 #include "ActuatorCoordinates.h"
36 #include "EndstopsPublicAccess.h"
37
38 #include "mbed.h" // for us_ticker_read()
39 #include "mri.h"
40
41 #include <fastmath.h>
42 #include <string>
43 #include <algorithm>
44
45 #define default_seek_rate_checksum CHECKSUM("default_seek_rate")
46 #define default_feed_rate_checksum CHECKSUM("default_feed_rate")
47 #define mm_per_line_segment_checksum CHECKSUM("mm_per_line_segment")
48 #define delta_segments_per_second_checksum CHECKSUM("delta_segments_per_second")
49 #define mm_per_arc_segment_checksum CHECKSUM("mm_per_arc_segment")
50 #define mm_max_arc_error_checksum CHECKSUM("mm_max_arc_error")
51 #define arc_correction_checksum CHECKSUM("arc_correction")
52 #define x_axis_max_speed_checksum CHECKSUM("x_axis_max_speed")
53 #define y_axis_max_speed_checksum CHECKSUM("y_axis_max_speed")
54 #define z_axis_max_speed_checksum CHECKSUM("z_axis_max_speed")
55 #define segment_z_moves_checksum CHECKSUM("segment_z_moves")
56 #define save_g92_checksum CHECKSUM("save_g92")
57 #define save_g54_checksum CHECKSUM("save_g54")
58 #define set_g92_checksum CHECKSUM("set_g92")
59
60 // arm solutions
61 #define arm_solution_checksum CHECKSUM("arm_solution")
62 #define cartesian_checksum CHECKSUM("cartesian")
63 #define rotatable_cartesian_checksum CHECKSUM("rotatable_cartesian")
64 #define rostock_checksum CHECKSUM("rostock")
65 #define linear_delta_checksum CHECKSUM("linear_delta")
66 #define rotary_delta_checksum CHECKSUM("rotary_delta")
67 #define delta_checksum CHECKSUM("delta")
68 #define hbot_checksum CHECKSUM("hbot")
69 #define corexy_checksum CHECKSUM("corexy")
70 #define corexz_checksum CHECKSUM("corexz")
71 #define kossel_checksum CHECKSUM("kossel")
72 #define morgan_checksum CHECKSUM("morgan")
73
74 // new-style actuator stuff
75 #define actuator_checksum CHEKCSUM("actuator")
76
77 #define step_pin_checksum CHECKSUM("step_pin")
78 #define dir_pin_checksum CHEKCSUM("dir_pin")
79 #define en_pin_checksum CHECKSUM("en_pin")
80
81 #define max_speed_checksum CHECKSUM("max_speed")
82 #define acceleration_checksum CHECKSUM("acceleration")
83 #define z_acceleration_checksum CHECKSUM("z_acceleration")
84
85 #define alpha_checksum CHECKSUM("alpha")
86 #define beta_checksum CHECKSUM("beta")
87 #define gamma_checksum CHECKSUM("gamma")
88
89 #define laser_module_default_power_checksum CHECKSUM("laser_module_default_power")
90
91 #define enable_checksum CHECKSUM("enable")
92 #define halt_checksum CHECKSUM("halt")
93 #define soft_endstop_checksum CHECKSUM("soft_endstop")
94 #define xmin_checksum CHECKSUM("x_min")
95 #define ymin_checksum CHECKSUM("y_min")
96 #define zmin_checksum CHECKSUM("z_min")
97 #define xmax_checksum CHECKSUM("x_max")
98 #define ymax_checksum CHECKSUM("y_max")
99 #define zmax_checksum CHECKSUM("z_max")
100
101 #define PI 3.14159265358979323846F // force to be float, do not use M_PI
102
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
106 Robot::Robot()
107 {
108 this->inch_mode = false;
109 this->absolute_mode = true;
110 this->e_absolute_mode = true;
111 this->select_plane(X_AXIS, Y_AXIS, Z_AXIS);
112 memset(this->machine_position, 0, sizeof machine_position);
113 memset(this->compensated_machine_position, 0, sizeof compensated_machine_position);
114 this->arm_solution = NULL;
115 seconds_per_minute = 60.0F;
116 this->clearToolOffset();
117 this->compensationTransform = nullptr;
118 this->get_e_scale_fnc= nullptr;
119 this->wcs_offsets.fill(wcs_t(0.0F, 0.0F, 0.0F));
120 this->g92_offset = wcs_t(0.0F, 0.0F, 0.0F);
121 this->next_command_is_MCS = false;
122 this->disable_segmentation= false;
123 this->disable_arm_solution= false;
124 this->n_motors= 0;
125 }
126
127 //Called when the module has just been loaded
128 void Robot::on_module_loaded()
129 {
130 this->register_for_event(ON_GCODE_RECEIVED);
131
132 // Configuration
133 this->load_config();
134 }
135
136 #define ACTUATOR_CHECKSUMS(X) { \
137 CHECKSUM(X "_step_pin"), \
138 CHECKSUM(X "_dir_pin"), \
139 CHECKSUM(X "_en_pin"), \
140 CHECKSUM(X "_steps_per_mm"), \
141 CHECKSUM(X "_max_rate"), \
142 CHECKSUM(X "_acceleration") \
143 }
144
145 void Robot::load_config()
146 {
147 // Arm solutions are used to convert positions in millimeters into position in steps for each stepper motor.
148 // While for a cartesian arm solution, this is a simple multiplication, in other, less simple cases, there is some serious math to be done.
149 // To make adding those solution easier, they have their own, separate object.
150 // Here we read the config to find out which arm solution to use
151 if (this->arm_solution) delete this->arm_solution;
152 int solution_checksum = get_checksum(THEKERNEL->config->value(arm_solution_checksum)->by_default("cartesian")->as_string());
153 // Note checksums are not const expressions when in debug mode, so don't use switch
154 if(solution_checksum == hbot_checksum || solution_checksum == corexy_checksum) {
155 this->arm_solution = new HBotSolution(THEKERNEL->config);
156
157 } else if(solution_checksum == corexz_checksum) {
158 this->arm_solution = new CoreXZSolution(THEKERNEL->config);
159
160 } else if(solution_checksum == rostock_checksum || solution_checksum == kossel_checksum || solution_checksum == delta_checksum || solution_checksum == linear_delta_checksum) {
161 this->arm_solution = new LinearDeltaSolution(THEKERNEL->config);
162
163 } else if(solution_checksum == rotatable_cartesian_checksum) {
164 this->arm_solution = new RotatableCartesianSolution(THEKERNEL->config);
165
166 } else if(solution_checksum == rotary_delta_checksum) {
167 this->arm_solution = new RotaryDeltaSolution(THEKERNEL->config);
168
169 } else if(solution_checksum == morgan_checksum) {
170 this->arm_solution = new MorganSCARASolution(THEKERNEL->config);
171
172 } else if(solution_checksum == cartesian_checksum) {
173 this->arm_solution = new CartesianSolution(THEKERNEL->config);
174
175 } else {
176 this->arm_solution = new CartesianSolution(THEKERNEL->config);
177 }
178
179 this->feed_rate = THEKERNEL->config->value(default_feed_rate_checksum )->by_default( 100.0F)->as_number();
180 this->seek_rate = THEKERNEL->config->value(default_seek_rate_checksum )->by_default( 100.0F)->as_number();
181 this->mm_per_line_segment = THEKERNEL->config->value(mm_per_line_segment_checksum )->by_default( 0.0F)->as_number();
182 this->delta_segments_per_second = THEKERNEL->config->value(delta_segments_per_second_checksum )->by_default(0.0f )->as_number();
183 this->mm_per_arc_segment = THEKERNEL->config->value(mm_per_arc_segment_checksum )->by_default( 0.0f)->as_number();
184 this->mm_max_arc_error = THEKERNEL->config->value(mm_max_arc_error_checksum )->by_default( 0.01f)->as_number();
185 this->arc_correction = THEKERNEL->config->value(arc_correction_checksum )->by_default( 5 )->as_number();
186
187 // in mm/sec but specified in config as mm/min
188 this->max_speeds[X_AXIS] = THEKERNEL->config->value(x_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
189 this->max_speeds[Y_AXIS] = THEKERNEL->config->value(y_axis_max_speed_checksum )->by_default(60000.0F)->as_number() / 60.0F;
190 this->max_speeds[Z_AXIS] = THEKERNEL->config->value(z_axis_max_speed_checksum )->by_default( 300.0F)->as_number() / 60.0F;
191 this->max_speed = THEKERNEL->config->value(max_speed_checksum )->by_default( -60.0F)->as_number() / 60.0F;
192
193 this->segment_z_moves = THEKERNEL->config->value(segment_z_moves_checksum )->by_default(true)->as_bool();
194 this->save_g92 = THEKERNEL->config->value(save_g92_checksum )->by_default(false)->as_bool();
195 this->save_g54 = THEKERNEL->config->value(save_g54_checksum )->by_default(THEKERNEL->is_grbl_mode())->as_bool();
196 string g92 = THEKERNEL->config->value(set_g92_checksum )->by_default("")->as_string();
197 if(!g92.empty()) {
198 // optional setting for a fixed G92 offset
199 std::vector<float> t= parse_number_list(g92.c_str());
200 if(t.size() == 3) {
201 g92_offset = wcs_t(t[0], t[1], t[2]);
202 }
203 }
204
205 // default s value for laser
206 this->s_value = THEKERNEL->config->value(laser_module_default_power_checksum)->by_default(0.8F)->as_number();
207
208 // Make our Primary XYZ StepperMotors, and potentially A B C
209 uint16_t const motor_checksums[][6] = {
210 ACTUATOR_CHECKSUMS("alpha"), // X
211 ACTUATOR_CHECKSUMS("beta"), // Y
212 ACTUATOR_CHECKSUMS("gamma"), // Z
213 #if MAX_ROBOT_ACTUATORS > 3
214 ACTUATOR_CHECKSUMS("delta"), // A
215 #if MAX_ROBOT_ACTUATORS > 4
216 ACTUATOR_CHECKSUMS("epsilon"), // B
217 #if MAX_ROBOT_ACTUATORS > 5
218 ACTUATOR_CHECKSUMS("zeta") // C
219 #endif
220 #endif
221 #endif
222 };
223
224 // default acceleration setting, can be overriden with newer per axis settings
225 this->default_acceleration= THEKERNEL->config->value(acceleration_checksum)->by_default(100.0F )->as_number(); // Acceleration is in mm/s^2
226
227 // make each motor
228 for (size_t a = 0; a < MAX_ROBOT_ACTUATORS; a++) {
229 Pin pins[3]; //step, dir, enable
230 for (size_t i = 0; i < 3; i++) {
231 pins[i].from_string(THEKERNEL->config->value(motor_checksums[a][i])->by_default("nc")->as_string())->as_output();
232 }
233
234 if(!pins[0].connected() || !pins[1].connected()) { // step and dir must be defined, but enable is optional
235 if(a <= Z_AXIS) {
236 THEKERNEL->streams->printf("FATAL: motor %c is not defined in config\n", 'X'+a);
237 n_motors= a; // we only have this number of motors
238 return;
239 }
240 break; // if any pin is not defined then the axis is not defined (and axis need to be defined in contiguous order)
241 }
242
243 StepperMotor *sm = new StepperMotor(pins[0], pins[1], pins[2]);
244 // register this motor (NB This must be 0,1,2) of the actuators array
245 uint8_t n= register_motor(sm);
246 if(n != a) {
247 // this is a fatal error
248 THEKERNEL->streams->printf("FATAL: motor %d does not match index %d\n", n, a);
249 return;
250 }
251
252 actuators[a]->change_steps_per_mm(THEKERNEL->config->value(motor_checksums[a][3])->by_default(a == 2 ? 2560.0F : 80.0F)->as_number());
253 actuators[a]->set_max_rate(THEKERNEL->config->value(motor_checksums[a][4])->by_default(30000.0F)->as_number()/60.0F); // it is in mm/min and converted to mm/sec
254 actuators[a]->set_acceleration(THEKERNEL->config->value(motor_checksums[a][5])->by_default(NAN)->as_number()); // mm/secs²
255 }
256
257 check_max_actuator_speeds(); // check the configs are sane
258
259 // if we have not specified a z acceleration see if the legacy config was set
260 if(isnan(actuators[Z_AXIS]->get_acceleration())) {
261 float acc= THEKERNEL->config->value(z_acceleration_checksum)->by_default(NAN)->as_number(); // disabled by default
262 if(!isnan(acc)) {
263 actuators[Z_AXIS]->set_acceleration(acc);
264 }
265 }
266
267 // initialise actuator positions to current cartesian position (X0 Y0 Z0)
268 // so the first move can be correct if homing is not performed
269 ActuatorCoordinates actuator_pos;
270 arm_solution->cartesian_to_actuator(machine_position, actuator_pos);
271 for (size_t i = 0; i < n_motors; i++)
272 actuators[i]->change_last_milestone(actuator_pos[i]);
273
274 //this->clearToolOffset();
275
276 soft_endstop_enabled= THEKERNEL->config->value(soft_endstop_checksum, enable_checksum)->by_default(false)->as_bool();
277 soft_endstop_halt= THEKERNEL->config->value(soft_endstop_checksum, halt_checksum)->by_default(true)->as_bool();
278
279 soft_endstop_min[X_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, xmin_checksum)->by_default(NAN)->as_number();
280 soft_endstop_min[Y_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, ymin_checksum)->by_default(NAN)->as_number();
281 soft_endstop_min[Z_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, zmin_checksum)->by_default(NAN)->as_number();
282 soft_endstop_max[X_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, xmax_checksum)->by_default(NAN)->as_number();
283 soft_endstop_max[Y_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, ymax_checksum)->by_default(NAN)->as_number();
284 soft_endstop_max[Z_AXIS]= THEKERNEL->config->value(soft_endstop_checksum, zmax_checksum)->by_default(NAN)->as_number();
285 }
286
287 uint8_t Robot::register_motor(StepperMotor *motor)
288 {
289 // register this motor with the step ticker
290 THEKERNEL->step_ticker->register_motor(motor);
291 if(n_motors >= k_max_actuators) {
292 // this is a fatal error
293 THEKERNEL->streams->printf("FATAL: too many motors, increase k_max_actuators\n");
294 __debugbreak();
295 }
296 actuators.push_back(motor);
297 motor->set_motor_id(n_motors);
298 return n_motors++;
299 }
300
301 void Robot::push_state()
302 {
303 bool am = this->absolute_mode;
304 bool em = this->e_absolute_mode;
305 bool im = this->inch_mode;
306 saved_state_t s(this->feed_rate, this->seek_rate, am, em, im, current_wcs);
307 state_stack.push(s);
308 }
309
310 void Robot::pop_state()
311 {
312 if(!state_stack.empty()) {
313 auto s = state_stack.top();
314 state_stack.pop();
315 this->feed_rate = std::get<0>(s);
316 this->seek_rate = std::get<1>(s);
317 this->absolute_mode = std::get<2>(s);
318 this->e_absolute_mode = std::get<3>(s);
319 this->inch_mode = std::get<4>(s);
320 this->current_wcs = std::get<5>(s);
321 }
322 }
323
324 std::vector<Robot::wcs_t> Robot::get_wcs_state() const
325 {
326 std::vector<wcs_t> v;
327 v.push_back(wcs_t(current_wcs, MAX_WCS, 0));
328 for(auto& i : wcs_offsets) {
329 v.push_back(i);
330 }
331 v.push_back(g92_offset);
332 v.push_back(tool_offset);
333 return v;
334 }
335
336 void Robot::get_current_machine_position(float *pos) const
337 {
338 // get real time current actuator position in mm
339 ActuatorCoordinates current_position{
340 actuators[X_AXIS]->get_current_position(),
341 actuators[Y_AXIS]->get_current_position(),
342 actuators[Z_AXIS]->get_current_position()
343 };
344
345 // get machine position from the actuator position using FK
346 arm_solution->actuator_to_cartesian(current_position, pos);
347 }
348
349 void Robot::print_position(uint8_t subcode, std::string& res, bool ignore_extruders) const
350 {
351 // M114.1 is a new way to do this (similar to how GRBL does it).
352 // it returns the realtime position based on the current step position of the actuators.
353 // this does require a FK to get a machine position from the actuator position
354 // and then invert all the transforms to get a workspace position from machine position
355 // M114 just does it the old way uses machine_position and does inverse transforms to get the requested position
356 uint32_t n = 0;
357 char buf[64];
358 if(subcode == 0) { // M114 print WCS
359 wcs_t pos= mcs2wcs(machine_position);
360 n = snprintf(buf, sizeof(buf), "C: X:%1.4f Y:%1.4f Z:%1.4f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
361
362 } else if(subcode == 4) {
363 // M114.4 print last milestone
364 n = snprintf(buf, sizeof(buf), "MP: X:%1.4f Y:%1.4f Z:%1.4f", machine_position[X_AXIS], machine_position[Y_AXIS], machine_position[Z_AXIS]);
365
366 } else if(subcode == 5) {
367 // M114.5 print last machine position (which should be the same as M114.1 if axis are not moving and no level compensation)
368 // will differ from LMS by the compensation at the current position otherwise
369 n = snprintf(buf, sizeof(buf), "CMP: X:%1.4f Y:%1.4f Z:%1.4f", compensated_machine_position[X_AXIS], compensated_machine_position[Y_AXIS], compensated_machine_position[Z_AXIS]);
370
371 } else {
372 // get real time positions
373 float mpos[3];
374 get_current_machine_position(mpos);
375
376 // current_position/mpos includes the compensation transform so we need to get the inverse to get actual position
377 if(compensationTransform) compensationTransform(mpos, true); // get inverse compensation transform
378
379 if(subcode == 1) { // M114.1 print realtime WCS
380 wcs_t pos= mcs2wcs(mpos);
381 n = snprintf(buf, sizeof(buf), "WCS: X:%1.4f Y:%1.4f Z:%1.4f", from_millimeters(std::get<X_AXIS>(pos)), from_millimeters(std::get<Y_AXIS>(pos)), from_millimeters(std::get<Z_AXIS>(pos)));
382
383 } else if(subcode == 2) { // M114.2 print realtime Machine coordinate system
384 n = snprintf(buf, sizeof(buf), "MCS: X:%1.4f Y:%1.4f Z:%1.4f", mpos[X_AXIS], mpos[Y_AXIS], mpos[Z_AXIS]);
385
386 } else if(subcode == 3) { // M114.3 print realtime actuator position
387 // get real time current actuator position in mm
388 ActuatorCoordinates current_position{
389 actuators[X_AXIS]->get_current_position(),
390 actuators[Y_AXIS]->get_current_position(),
391 actuators[Z_AXIS]->get_current_position()
392 };
393 n = snprintf(buf, sizeof(buf), "APOS: X:%1.4f Y:%1.4f Z:%1.4f", current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]);
394 }
395 }
396
397 if(n > sizeof(buf)) n= sizeof(buf);
398 res.append(buf, n);
399
400 #if MAX_ROBOT_ACTUATORS > 3
401 // deal with the ABC axis
402 for (int i = A_AXIS; i < n_motors; ++i) {
403 n= 0;
404 if(ignore_extruders && actuators[i]->is_extruder()) continue; // don't show an extruder as that will be E
405 if(subcode == 4) { // M114.4 print last milestone
406 n= snprintf(buf, sizeof(buf), " %c:%1.4f", 'A'+i-A_AXIS, machine_position[i]);
407
408 }else if(subcode == 2 || subcode == 3) { // M114.2/M114.3 print actuator position which is the same as machine position for ABC
409 // current actuator position
410 n= snprintf(buf, sizeof(buf), " %c:%1.4f", 'A'+i-A_AXIS, actuators[i]->get_current_position());
411 }
412 if(n > sizeof(buf)) n= sizeof(buf);
413 if(n > 0) res.append(buf, n);
414 }
415 #endif
416 }
417
418 // converts current last milestone (machine position without compensation transform) to work coordinate system (inverse transform)
419 Robot::wcs_t Robot::mcs2wcs(const Robot::wcs_t& pos) const
420 {
421 return std::make_tuple(
422 std::get<X_AXIS>(pos) - std::get<X_AXIS>(wcs_offsets[current_wcs]) + std::get<X_AXIS>(g92_offset) - std::get<X_AXIS>(tool_offset),
423 std::get<Y_AXIS>(pos) - std::get<Y_AXIS>(wcs_offsets[current_wcs]) + std::get<Y_AXIS>(g92_offset) - std::get<Y_AXIS>(tool_offset),
424 std::get<Z_AXIS>(pos) - std::get<Z_AXIS>(wcs_offsets[current_wcs]) + std::get<Z_AXIS>(g92_offset) - std::get<Z_AXIS>(tool_offset)
425 );
426 }
427
428 // this does a sanity check that actuator speeds do not exceed steps rate capability
429 // we will override the actuator max_rate if the combination of max_rate and steps/sec exceeds base_stepping_frequency
430 void Robot::check_max_actuator_speeds()
431 {
432 for (size_t i = 0; i < n_motors; i++) {
433 if(actuators[i]->is_extruder()) continue; //extruders are not included in this check
434
435 float step_freq = actuators[i]->get_max_rate() * actuators[i]->get_steps_per_mm();
436 if (step_freq > THEKERNEL->base_stepping_frequency) {
437 actuators[i]->set_max_rate(floorf(THEKERNEL->base_stepping_frequency / actuators[i]->get_steps_per_mm()));
438 THEKERNEL->streams->printf("WARNING: actuator %d rate exceeds base_stepping_frequency * ..._steps_per_mm: %f, setting to %f\n", i, step_freq, actuators[i]->get_max_rate());
439 }
440 }
441 }
442
443 //A GCode has been received
444 //See if the current Gcode line has some orders for us
445 void Robot::on_gcode_received(void *argument)
446 {
447 Gcode *gcode = static_cast<Gcode *>(argument);
448
449 enum MOTION_MODE_T motion_mode= NONE;
450
451 if( gcode->has_g) {
452 switch( gcode->g ) {
453 case 0: motion_mode = SEEK; break;
454 case 1: motion_mode = LINEAR; break;
455 case 2: motion_mode = CW_ARC; break;
456 case 3: motion_mode = CCW_ARC; break;
457 case 4: { // G4 Dwell
458 uint32_t delay_ms = 0;
459 if (gcode->has_letter('P')) {
460 if(THEKERNEL->is_grbl_mode()) {
461 // in grbl mode (and linuxcnc) P is decimal seconds
462 float f= gcode->get_value('P');
463 delay_ms= f * 1000.0F;
464
465 }else{
466 // in reprap P is milliseconds, they always have to be different!
467 delay_ms = gcode->get_int('P');
468 }
469 }
470 if (gcode->has_letter('S')) {
471 delay_ms += gcode->get_int('S') * 1000;
472 }
473 if (delay_ms > 0) {
474 // drain queue
475 THEKERNEL->conveyor->wait_for_idle();
476 // wait for specified time
477 uint32_t start = us_ticker_read(); // mbed call
478 while ((us_ticker_read() - start) < delay_ms * 1000) {
479 THEKERNEL->call_event(ON_IDLE, this);
480 if(THEKERNEL->is_halted()) return;
481 }
482 }
483 }
484 break;
485
486 case 10: // G10 L2 [L20] Pn Xn Yn Zn set WCS
487 if(gcode->has_letter('L') && (gcode->get_int('L') == 2 || gcode->get_int('L') == 20) && gcode->has_letter('P')) {
488 size_t n = gcode->get_uint('P');
489 if(n == 0) n = current_wcs; // set current coordinate system
490 else --n;
491 if(n < MAX_WCS) {
492 float x, y, z;
493 std::tie(x, y, z) = wcs_offsets[n];
494 if(gcode->get_int('L') == 20) {
495 // this makes the current machine position (less compensation transform) the offset
496 // get current position in WCS
497 wcs_t pos= mcs2wcs(machine_position);
498
499 if(gcode->has_letter('X')){
500 x -= to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
501 }
502
503 if(gcode->has_letter('Y')){
504 y -= to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
505 }
506 if(gcode->has_letter('Z')) {
507 z -= to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
508 }
509
510 } else {
511 if(absolute_mode) {
512 // the value is the offset from machine zero
513 if(gcode->has_letter('X')) x = to_millimeters(gcode->get_value('X'));
514 if(gcode->has_letter('Y')) y = to_millimeters(gcode->get_value('Y'));
515 if(gcode->has_letter('Z')) z = to_millimeters(gcode->get_value('Z'));
516 }else{
517 if(gcode->has_letter('X')) x += to_millimeters(gcode->get_value('X'));
518 if(gcode->has_letter('Y')) y += to_millimeters(gcode->get_value('Y'));
519 if(gcode->has_letter('Z')) z += to_millimeters(gcode->get_value('Z'));
520 }
521 }
522 wcs_offsets[n] = wcs_t(x, y, z);
523 }
524 }
525 break;
526
527 case 17: this->select_plane(X_AXIS, Y_AXIS, Z_AXIS); break;
528 case 18: this->select_plane(X_AXIS, Z_AXIS, Y_AXIS); break;
529 case 19: this->select_plane(Y_AXIS, Z_AXIS, X_AXIS); break;
530 case 20: this->inch_mode = true; break;
531 case 21: this->inch_mode = false; break;
532
533 case 54: case 55: case 56: case 57: case 58: case 59:
534 // select WCS 0-8: G54..G59, G59.1, G59.2, G59.3
535 current_wcs = gcode->g - 54;
536 if(gcode->g == 59 && gcode->subcode > 0) {
537 current_wcs += gcode->subcode;
538 if(current_wcs >= MAX_WCS) current_wcs = MAX_WCS - 1;
539 }
540 break;
541
542 case 90: this->absolute_mode = true; this->e_absolute_mode = true; break;
543 case 91: this->absolute_mode = false; this->e_absolute_mode = false; break;
544
545 case 92: {
546 if(gcode->subcode == 1 || gcode->subcode == 2 || gcode->get_num_args() == 0) {
547 // reset G92 offsets to 0
548 g92_offset = wcs_t(0, 0, 0);
549
550 } else if(gcode->subcode == 3) {
551 // initialize G92 to the specified values, only used for saving it with M500
552 float x= 0, y= 0, z= 0;
553 if(gcode->has_letter('X')) x= gcode->get_value('X');
554 if(gcode->has_letter('Y')) y= gcode->get_value('Y');
555 if(gcode->has_letter('Z')) z= gcode->get_value('Z');
556 g92_offset = wcs_t(x, y, z);
557
558 } else {
559 // standard setting of the g92 offsets, making current WCS position whatever the coordinate arguments are
560 float x, y, z;
561 std::tie(x, y, z) = g92_offset;
562 // get current position in WCS
563 wcs_t pos= mcs2wcs(machine_position);
564
565 // adjust g92 offset to make the current wpos == the value requested
566 if(gcode->has_letter('X')){
567 x += to_millimeters(gcode->get_value('X')) - std::get<X_AXIS>(pos);
568 }
569 if(gcode->has_letter('Y')){
570 y += to_millimeters(gcode->get_value('Y')) - std::get<Y_AXIS>(pos);
571 }
572 if(gcode->has_letter('Z')) {
573 z += to_millimeters(gcode->get_value('Z')) - std::get<Z_AXIS>(pos);
574 }
575 g92_offset = wcs_t(x, y, z);
576 }
577
578 #if MAX_ROBOT_ACTUATORS > 3
579 if(gcode->subcode == 0 && (gcode->has_letter('E') || gcode->get_num_args() == 0)){
580 // reset the E position, legacy for 3d Printers to be reprap compatible
581 // find the selected extruder
582 int selected_extruder= get_active_extruder();
583 if(selected_extruder > 0) {
584 float e= gcode->has_letter('E') ? gcode->get_value('E') : 0;
585 machine_position[selected_extruder]= compensated_machine_position[selected_extruder]= e;
586 actuators[selected_extruder]->change_last_milestone(get_e_scale_fnc ? e*get_e_scale_fnc() : e);
587 }
588 }
589 #endif
590
591 return;
592 }
593 }
594
595 } else if( gcode->has_m) {
596 switch( gcode->m ) {
597 // case 0: // M0 feed hold, (M0.1 is release feed hold, except we are in feed hold)
598 // if(THEKERNEL->is_grbl_mode()) THEKERNEL->set_feed_hold(gcode->subcode == 0);
599 // break;
600
601 case 30: // M30 end of program in grbl mode (otherwise it is delete sdcard file)
602 if(!THEKERNEL->is_grbl_mode()) break;
603 // fall through to M2
604 case 2: // M2 end of program
605 current_wcs = 0;
606 absolute_mode = true;
607 break;
608 case 17:
609 THEKERNEL->call_event(ON_ENABLE, (void*)1); // turn all enable pins on
610 break;
611
612 case 18: // this allows individual motors to be turned off, no parameters falls through to turn all off
613 if(gcode->get_num_args() > 0) {
614 // bitmap of motors to turn off, where bit 1:X, 2:Y, 3:Z, 4:A, 5:B, 6:C
615 uint32_t bm= 0;
616 for (int i = 0; i < n_motors; ++i) {
617 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-3));
618 if(gcode->has_letter(axis)) bm |= (0x02<<i); // set appropriate bit
619 }
620 // handle E parameter as currently selected extruder ABC
621 if(gcode->has_letter('E')) {
622 // find first selected extruder
623 int i= get_active_extruder();
624 if(i > 0) {
625 bm |= (0x02<<i); // set appropriate bit
626 }
627 }
628
629 THEKERNEL->conveyor->wait_for_idle();
630 THEKERNEL->call_event(ON_ENABLE, (void *)bm);
631 break;
632 }
633 // fall through
634 case 84:
635 THEKERNEL->conveyor->wait_for_idle();
636 THEKERNEL->call_event(ON_ENABLE, nullptr); // turn all enable pins off
637 break;
638
639 case 82: e_absolute_mode= true; break;
640 case 83: e_absolute_mode= false; break;
641
642 case 92: // M92 - set steps per mm
643 for (int i = 0; i < n_motors; ++i) {
644 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
645 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
646 if(gcode->has_letter(axis)) {
647 actuators[i]->change_steps_per_mm(this->to_millimeters(gcode->get_value(axis)));
648 }
649 gcode->stream->printf("%c:%f ", axis, actuators[i]->get_steps_per_mm());
650 }
651 gcode->add_nl = true;
652 check_max_actuator_speeds();
653 return;
654
655 case 114:{
656 std::string buf;
657 print_position(gcode->subcode, buf, true); // ignore extruders as they will print E themselves
658 gcode->txt_after_ok.append(buf);
659 return;
660 }
661
662 case 120: // push state
663 push_state();
664 break;
665
666 case 121: // pop state
667 pop_state();
668 break;
669
670 case 203: // M203 Set maximum feedrates in mm/sec, M203.1 set maximum actuator feedrates
671 if(gcode->get_num_args() == 0) {
672 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
673 gcode->stream->printf(" %c: %g ", 'X' + i, gcode->subcode == 0 ? this->max_speeds[i] : actuators[i]->get_max_rate());
674 }
675 if(gcode->subcode == 1) {
676 for (size_t i = A_AXIS; i < n_motors; i++) {
677 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
678 gcode->stream->printf(" %c: %g ", 'A' + i - A_AXIS, actuators[i]->get_max_rate());
679 }
680 }else{
681 gcode->stream->printf(" S: %g ", this->max_speed);
682 }
683
684 gcode->add_nl = true;
685
686 }else{
687 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
688 if (gcode->has_letter('X' + i)) {
689 float v= gcode->get_value('X'+i);
690 if(gcode->subcode == 0) this->max_speeds[i]= v;
691 else if(gcode->subcode == 1) actuators[i]->set_max_rate(v);
692 }
693 }
694
695 if(gcode->subcode == 1) {
696 // ABC axis only handle actuator max speeds
697 for (size_t i = A_AXIS; i < n_motors; i++) {
698 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
699 int c= 'A' + i - A_AXIS;
700 if(gcode->has_letter(c)) {
701 float v= gcode->get_value(c);
702 actuators[i]->set_max_rate(v);
703 }
704 }
705
706 }else{
707 if(gcode->has_letter('S')) max_speed= gcode->get_value('S');
708 }
709
710
711 // this format is deprecated
712 if(gcode->subcode == 0 && (gcode->has_letter('A') || gcode->has_letter('B') || gcode->has_letter('C'))) {
713 gcode->stream->printf("NOTE this format is deprecated, Use M203.1 instead\n");
714 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
715 if (gcode->has_letter('A' + i)) {
716 float v= gcode->get_value('A'+i);
717 actuators[i]->set_max_rate(v);
718 }
719 }
720 }
721
722 if(gcode->subcode == 1) check_max_actuator_speeds();
723 }
724 break;
725
726 case 204: // M204 Snnn - set default acceleration to nnn, Xnnn Ynnn Znnn sets axis specific acceleration
727 if (gcode->has_letter('S')) {
728 float acc = gcode->get_value('S'); // mm/s^2
729 // enforce minimum
730 if (acc < 1.0F) acc = 1.0F;
731 this->default_acceleration = acc;
732 }
733 for (int i = 0; i < n_motors; ++i) {
734 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
735 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
736 if(gcode->has_letter(axis)) {
737 float acc = gcode->get_value(axis); // mm/s^2
738 // enforce positive
739 if (acc <= 0.0F) acc = NAN;
740 actuators[i]->set_acceleration(acc);
741 }
742 }
743 break;
744
745 case 205: // M205 Xnnn - set junction deviation, Z - set Z junction deviation, Snnn - Set minimum planner speed
746 if (gcode->has_letter('X')) {
747 float jd = gcode->get_value('X');
748 // enforce minimum
749 if (jd < 0.0F)
750 jd = 0.0F;
751 THEKERNEL->planner->junction_deviation = jd;
752 }
753 if (gcode->has_letter('Z')) {
754 float jd = gcode->get_value('Z');
755 // enforce minimum, -1 disables it and uses regular junction deviation
756 if (jd <= -1.0F)
757 jd = NAN;
758 THEKERNEL->planner->z_junction_deviation = jd;
759 }
760 if (gcode->has_letter('S')) {
761 float mps = gcode->get_value('S');
762 // enforce minimum
763 if (mps < 0.0F)
764 mps = 0.0F;
765 THEKERNEL->planner->minimum_planner_speed = mps;
766 }
767 break;
768
769 case 211: // M211 Sn turns soft endstops on/off
770 if(gcode->has_letter('S')) {
771 soft_endstop_enabled= gcode->get_uint('S') == 1;
772 }else{
773 gcode->stream->printf("Soft endstops are %s", soft_endstop_enabled ? "Enabled" : "Disabled");
774 for (int i = X_AXIS; i <= Z_AXIS; ++i) {
775 if(isnan(soft_endstop_min[i])) {
776 gcode->stream->printf(",%c min is disabled", 'X'+i);
777 }
778 if(isnan(soft_endstop_max[i])) {
779 gcode->stream->printf(",%c max is disabled", 'X'+i);
780 }
781 if(!is_homed(i)) {
782 gcode->stream->printf(",%c axis is not homed", 'X'+i);
783 }
784 }
785 gcode->stream->printf("\n");
786 }
787 break;
788
789 case 220: // M220 - speed override percentage
790 if (gcode->has_letter('S')) {
791 float factor = gcode->get_value('S');
792 // enforce minimum 10% speed
793 if (factor < 10.0F)
794 factor = 10.0F;
795 // enforce maximum 10x speed
796 if (factor > 1000.0F)
797 factor = 1000.0F;
798
799 seconds_per_minute = 6000.0F / factor;
800 } else {
801 gcode->stream->printf("Speed factor at %6.2f %%\n", 6000.0F / seconds_per_minute);
802 }
803 break;
804
805 case 400: // wait until all moves are done up to this point
806 THEKERNEL->conveyor->wait_for_idle();
807 break;
808
809 case 500: // M500 saves some volatile settings to config override file
810 case 503: { // M503 just prints the settings
811 gcode->stream->printf(";Steps per unit:\nM92 ");
812 for (int i = 0; i < n_motors; ++i) {
813 if(actuators[i]->is_extruder()) continue; //extruders handle this themselves
814 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
815 gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_steps_per_mm());
816 }
817 gcode->stream->printf("\n");
818
819 // only print if not NAN
820 gcode->stream->printf(";Acceleration mm/sec^2:\nM204 S%1.5f ", default_acceleration);
821 for (int i = 0; i < n_motors; ++i) {
822 if(actuators[i]->is_extruder()) continue; // extruders handle this themselves
823 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
824 if(!isnan(actuators[i]->get_acceleration())) gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_acceleration());
825 }
826 gcode->stream->printf("\n");
827
828 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, isnan(THEKERNEL->planner->z_junction_deviation)?-1:THEKERNEL->planner->z_junction_deviation, THEKERNEL->planner->minimum_planner_speed);
829
830 gcode->stream->printf(";Max cartesian feedrates in mm/sec:\nM203 X%1.5f Y%1.5f Z%1.5f S%1.5f\n", this->max_speeds[X_AXIS], this->max_speeds[Y_AXIS], this->max_speeds[Z_AXIS], this->max_speed);
831
832 gcode->stream->printf(";Max actuator feedrates in mm/sec:\nM203.1 ");
833 for (int i = 0; i < n_motors; ++i) {
834 if(actuators[i]->is_extruder()) continue; // extruders handle this themselves
835 char axis= (i <= Z_AXIS ? 'X'+i : 'A'+(i-A_AXIS));
836 gcode->stream->printf("%c%1.5f ", axis, actuators[i]->get_max_rate());
837 }
838 gcode->stream->printf("\n");
839
840 // get or save any arm solution specific optional values
841 BaseSolution::arm_options_t options;
842 if(arm_solution->get_optional(options) && !options.empty()) {
843 gcode->stream->printf(";Optional arm solution specific settings:\nM665");
844 for(auto &i : options) {
845 gcode->stream->printf(" %c%1.4f", i.first, i.second);
846 }
847 gcode->stream->printf("\n");
848 }
849
850 // save wcs_offsets and current_wcs
851 // TODO this may need to be done whenever they change to be compliant
852 if(save_g54) {
853 gcode->stream->printf(";WCS settings\n");
854 gcode->stream->printf("%s\n", wcs2gcode(current_wcs).c_str());
855 int n = 1;
856 for(auto &i : wcs_offsets) {
857 if(i != wcs_t(0, 0, 0)) {
858 float x, y, z;
859 std::tie(x, y, z) = i;
860 gcode->stream->printf("G10 L2 P%d X%f Y%f Z%f ; %s\n", n, x, y, z, wcs2gcode(n-1).c_str());
861 }
862 ++n;
863 }
864 }
865 if(save_g92) {
866 // linuxcnc saves G92, so we do too if configured, default is to not save to maintain backward compatibility
867 // also it needs to be used to set Z0 on rotary deltas as M206/306 can't be used, so saving it is necessary in that case
868 if(g92_offset != wcs_t(0, 0, 0)) {
869 float x, y, z;
870 std::tie(x, y, z) = g92_offset;
871 gcode->stream->printf("G92.3 X%f Y%f Z%f\n", x, y, z); // sets G92 to the specified values
872 }
873 }
874 }
875 break;
876
877 case 665: { // M665 set optional arm solution variables based on arm solution.
878 // the parameter args could be any letter each arm solution only accepts certain ones
879 BaseSolution::arm_options_t options = gcode->get_args();
880 options.erase('S'); // don't include the S
881 options.erase('U'); // don't include the U
882 if(options.size() > 0) {
883 // set the specified options
884 arm_solution->set_optional(options);
885 }
886 options.clear();
887 if(arm_solution->get_optional(options)) {
888 // foreach optional value
889 for(auto &i : options) {
890 // print all current values of supported options
891 gcode->stream->printf("%c: %8.4f ", i.first, i.second);
892 gcode->add_nl = true;
893 }
894 }
895
896 if(gcode->has_letter('S')) { // set delta segments per second, not saved by M500
897 this->delta_segments_per_second = gcode->get_value('S');
898 gcode->stream->printf("Delta segments set to %8.4f segs/sec\n", this->delta_segments_per_second);
899
900 } else if(gcode->has_letter('U')) { // or set mm_per_line_segment, not saved by M500
901 this->mm_per_line_segment = gcode->get_value('U');
902 this->delta_segments_per_second = 0;
903 gcode->stream->printf("mm per line segment set to %8.4f\n", this->mm_per_line_segment);
904 }
905
906 break;
907 }
908 }
909 }
910
911 if( motion_mode != NONE) {
912 is_g123= motion_mode != SEEK;
913 process_move(gcode, motion_mode);
914
915 }else{
916 is_g123= false;
917 }
918
919 next_command_is_MCS = false; // must be on same line as G0 or G1
920 }
921
922 int Robot::get_active_extruder() const
923 {
924 for (int i = E_AXIS; i < n_motors; ++i) {
925 // find first selected extruder
926 if(actuators[i]->is_extruder() && actuators[i]->is_selected()) return i;
927 }
928 return 0;
929 }
930
931 // process a G0/G1/G2/G3
932 void Robot::process_move(Gcode *gcode, enum MOTION_MODE_T motion_mode)
933 {
934 // we have a G0/G1/G2/G3 so extract parameters and apply offsets to get machine coordinate target
935 // get XYZ and one E (which goes to the selected extruder)
936 float param[4]{NAN, NAN, NAN, NAN};
937
938 // process primary axis
939 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
940 char letter= 'X'+i;
941 if( gcode->has_letter(letter) ) {
942 param[i] = this->to_millimeters(gcode->get_value(letter));
943 }
944 }
945
946 float offset[3]{0,0,0};
947 for(char letter = 'I'; letter <= 'K'; letter++) {
948 if( gcode->has_letter(letter) ) {
949 offset[letter - 'I'] = this->to_millimeters(gcode->get_value(letter));
950 }
951 }
952
953 // calculate target in machine coordinates (less compensation transform which needs to be done after segmentation)
954 float target[n_motors];
955 memcpy(target, machine_position, n_motors*sizeof(float));
956
957 if(!next_command_is_MCS) {
958 if(this->absolute_mode) {
959 // apply wcs offsets and g92 offset and tool offset
960 if(!isnan(param[X_AXIS])) {
961 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);
962 }
963
964 if(!isnan(param[Y_AXIS])) {
965 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);
966 }
967
968 if(!isnan(param[Z_AXIS])) {
969 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);
970 }
971
972 }else{
973 // they are deltas from the machine_position if specified
974 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
975 if(!isnan(param[i])) target[i] = param[i] + machine_position[i];
976 }
977 }
978
979 }else{
980 // already in machine coordinates, we do not add wcs or tool offset for that
981 for(int i= X_AXIS; i <= Z_AXIS; ++i) {
982 if(!isnan(param[i])) target[i] = param[i];
983 }
984 }
985
986 float delta_e= NAN;
987
988 #if MAX_ROBOT_ACTUATORS > 3
989 // process extruder parameters, for active extruder only (only one active extruder at a time)
990 int selected_extruder= 0;
991 if(gcode->has_letter('E')) {
992 selected_extruder= get_active_extruder();
993 param[E_AXIS]= gcode->get_value('E');
994 }
995
996 // do E for the selected extruder
997 if(selected_extruder > 0 && !isnan(param[E_AXIS])) {
998 if(this->e_absolute_mode) {
999 target[selected_extruder]= param[E_AXIS];
1000 delta_e= target[selected_extruder] - machine_position[selected_extruder];
1001 }else{
1002 delta_e= param[E_AXIS];
1003 target[selected_extruder] = delta_e + machine_position[selected_extruder];
1004 }
1005 }
1006
1007 // process ABC axis, this is mutually exclusive to using E for an extruder, so if E is used and A then the results are undefined
1008 for (int i = A_AXIS; i < n_motors; ++i) {
1009 char letter= 'A'+i-A_AXIS;
1010 if(gcode->has_letter(letter)) {
1011 float p= gcode->get_value(letter);
1012 if(this->absolute_mode) {
1013 target[i]= p;
1014 }else{
1015 target[i]= p + machine_position[i];
1016 }
1017 }
1018 }
1019 #endif
1020
1021 if( gcode->has_letter('F') ) {
1022 if( motion_mode == SEEK )
1023 this->seek_rate = this->to_millimeters( gcode->get_value('F') );
1024 else
1025 this->feed_rate = this->to_millimeters( gcode->get_value('F') );
1026 }
1027
1028 // S is modal When specified on a G0/1/2/3 command
1029 if(gcode->has_letter('S')) s_value= gcode->get_value('S');
1030
1031 bool moved= false;
1032
1033 // Perform any physical actions
1034 switch(motion_mode) {
1035 case NONE: break;
1036
1037 case SEEK:
1038 moved= this->append_line(gcode, target, this->seek_rate / seconds_per_minute, delta_e );
1039 break;
1040
1041 case LINEAR:
1042 moved= this->append_line(gcode, target, this->feed_rate / seconds_per_minute, delta_e );
1043 break;
1044
1045 case CW_ARC:
1046 case CCW_ARC:
1047 // Note arcs are not currently supported by extruder based machines, as 3D slicers do not use arcs (G2/G3)
1048 moved= this->compute_arc(gcode, offset, target, motion_mode);
1049 break;
1050 }
1051
1052 // needed to act as start of next arc command
1053 memcpy(arc_milestone, target, sizeof(arc_milestone));
1054
1055 if(moved) {
1056 // set machine_position to the calculated target
1057 memcpy(machine_position, target, n_motors*sizeof(float));
1058 }
1059 }
1060
1061 // reset the machine position for all axis. Used for homing.
1062 // after homing we supply the cartesian coordinates that the head is at when homed,
1063 // however for Z this is the compensated machine position (if enabled)
1064 // So we need to apply the inverse compensation transform to the supplied coordinates to get the correct machine position
1065 // this will make the results from M114 and ? consistent after homing.
1066 // This works for cases where the Z endstop is fixed on the Z actuator and is the same regardless of where XY are.
1067 void Robot::reset_axis_position(float x, float y, float z)
1068 {
1069 // set both the same initially
1070 compensated_machine_position[X_AXIS]= machine_position[X_AXIS] = x;
1071 compensated_machine_position[Y_AXIS]= machine_position[Y_AXIS] = y;
1072 compensated_machine_position[Z_AXIS]= machine_position[Z_AXIS] = z;
1073
1074 if(compensationTransform) {
1075 // apply inverse transform to get machine_position
1076 compensationTransform(machine_position, true);
1077 }
1078
1079 // now set the actuator positions based on the supplied compensated position
1080 ActuatorCoordinates actuator_pos;
1081 arm_solution->cartesian_to_actuator(this->compensated_machine_position, actuator_pos);
1082 for (size_t i = X_AXIS; i <= Z_AXIS; i++)
1083 actuators[i]->change_last_milestone(actuator_pos[i]);
1084 }
1085
1086 // Reset the position for an axis (used in homing, and to reset extruder after suspend)
1087 void Robot::reset_axis_position(float position, int axis)
1088 {
1089 compensated_machine_position[axis] = position;
1090 if(axis <= Z_AXIS) {
1091 reset_axis_position(compensated_machine_position[X_AXIS], compensated_machine_position[Y_AXIS], compensated_machine_position[Z_AXIS]);
1092
1093 #if MAX_ROBOT_ACTUATORS > 3
1094 }else if(axis < n_motors) {
1095 // ABC and/or extruders need to be set as there is no arm solution for them
1096 machine_position[axis]= compensated_machine_position[axis]= position;
1097 actuators[axis]->change_last_milestone(machine_position[axis]);
1098 #endif
1099 }
1100 }
1101
1102 // similar to reset_axis_position but directly sets the actuator positions in actuators units (eg mm for cartesian, degrees for rotary delta)
1103 // then sets the axis positions to match. currently only called from Endstops.cpp and RotaryDeltaCalibration.cpp
1104 void Robot::reset_actuator_position(const ActuatorCoordinates &ac)
1105 {
1106 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
1107 if(!isnan(ac[i])) actuators[i]->change_last_milestone(ac[i]);
1108 }
1109
1110 // now correct axis positions then recorrect actuator to account for rounding
1111 reset_position_from_current_actuator_position();
1112 }
1113
1114 // Use FK to find out where actuator is and reset to match
1115 // TODO maybe we should only reset axis that are being homed unless this is due to a ON_HALT
1116 void Robot::reset_position_from_current_actuator_position()
1117 {
1118 ActuatorCoordinates actuator_pos;
1119 for (size_t i = X_AXIS; i < n_motors; i++) {
1120 // NOTE actuator::current_position is curently NOT the same as actuator::machine_position after an abrupt abort
1121 actuator_pos[i] = actuators[i]->get_current_position();
1122 }
1123
1124 // discover machine position from where actuators actually are
1125 arm_solution->actuator_to_cartesian(actuator_pos, compensated_machine_position);
1126 memcpy(machine_position, compensated_machine_position, sizeof machine_position);
1127
1128 // compensated_machine_position includes the compensation transform so we need to get the inverse to get actual machine_position
1129 if(compensationTransform) compensationTransform(machine_position, true); // get inverse compensation transform
1130
1131 // now reset actuator::machine_position, NOTE this may lose a little precision as FK is not always entirely accurate.
1132 // NOTE This is required to sync the machine position with the actuator position, we do a somewhat redundant cartesian_to_actuator() call
1133 // to get everything in perfect sync.
1134 arm_solution->cartesian_to_actuator(compensated_machine_position, actuator_pos);
1135 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
1136 actuators[i]->change_last_milestone(actuator_pos[i]);
1137 }
1138
1139 // Handle extruders and/or ABC axis
1140 #if MAX_ROBOT_ACTUATORS > 3
1141 for (int i = A_AXIS; i < n_motors; i++) {
1142 // ABC and/or extruders just need to set machine_position and compensated_machine_position
1143 float ap= actuator_pos[i];
1144 if(actuators[i]->is_extruder() && get_e_scale_fnc) ap /= get_e_scale_fnc(); // inverse E scale if there is one and this is an extruder
1145 machine_position[i]= compensated_machine_position[i]= ap;
1146 actuators[i]->change_last_milestone(actuator_pos[i]); // this updates the last_milestone in the actuator
1147 }
1148 #endif
1149 }
1150
1151 // Convert target (in machine coordinates) to machine_position, then convert to actuator position and append this to the planner
1152 // target is in machine coordinates without the compensation transform, however we save a compensated_machine_position that includes
1153 // all transforms and is what we actually convert to actuator positions
1154 bool Robot::append_milestone(const float target[], float rate_mm_s)
1155 {
1156 float deltas[n_motors];
1157 float transformed_target[n_motors]; // adjust target for bed compensation
1158 float unit_vec[N_PRIMARY_AXIS];
1159
1160 // unity transform by default
1161 memcpy(transformed_target, target, n_motors*sizeof(float));
1162
1163 // check function pointer and call if set to transform the target to compensate for bed
1164 if(compensationTransform) {
1165 // some compensation strategies can transform XYZ, some just change Z
1166 compensationTransform(transformed_target, false);
1167 }
1168
1169 // check soft endstops only for homed axis that are enabled
1170 if(soft_endstop_enabled) {
1171 for (int i = 0; i <= Z_AXIS; ++i) {
1172 if(!is_homed(i)) continue;
1173 if( (!isnan(soft_endstop_min[i]) && transformed_target[i] < soft_endstop_min[i]) || (!isnan(soft_endstop_max[i]) && transformed_target[i] > soft_endstop_max[i]) ) {
1174 if(soft_endstop_halt) {
1175 if(THEKERNEL->is_grbl_mode()) {
1176 THEKERNEL->streams->printf("error:");
1177 }else{
1178 THEKERNEL->streams->printf("Error: ");
1179 }
1180
1181 THEKERNEL->streams->printf("Soft Endstop %c was exceeded - reset or $X or M999 required\n", i+'X');
1182 THEKERNEL->call_event(ON_HALT, nullptr);
1183 return false;
1184
1185 //} else if(soft_endstop_truncate) {
1186 // TODO VERY hard to do need to go back and change the target, and calculate intercept with the edge
1187 // and store all preceding vectors that have on eor more points ourtside of bounds so we can create a propper clip against the boundaries
1188
1189 } else {
1190 // ignore it
1191 if(THEKERNEL->is_grbl_mode()) {
1192 THEKERNEL->streams->printf("error:");
1193 }else{
1194 THEKERNEL->streams->printf("Error: ");
1195 }
1196 THEKERNEL->streams->printf("Soft Endstop %c was exceeded - entire move ignored\n", i+'X');
1197 return false;
1198 }
1199 }
1200 }
1201 }
1202
1203
1204 bool move= false;
1205 float sos= 0; // sum of squares for just primary axis (XYZ usually)
1206
1207 // find distance moved by each axis, use transformed target from the current compensated machine position
1208 for (size_t i = 0; i < n_motors; i++) {
1209 deltas[i] = transformed_target[i] - compensated_machine_position[i];
1210 if(deltas[i] == 0) continue;
1211 // at least one non zero delta
1212 move = true;
1213 if(i < N_PRIMARY_AXIS) {
1214 sos += powf(deltas[i], 2);
1215 }
1216 }
1217
1218 // nothing moved
1219 if(!move) return false;
1220
1221 // see if this is a primary axis move or not
1222 bool auxilliary_move= true;
1223 for (int i = 0; i < N_PRIMARY_AXIS; ++i) {
1224 if(deltas[i] != 0) {
1225 auxilliary_move= false;
1226 break;
1227 }
1228 }
1229
1230 // total movement, use XYZ if a primary axis otherwise we calculate distance for E after scaling to mm
1231 float distance= auxilliary_move ? 0 : sqrtf(sos);
1232
1233 // it is unlikely but we need to protect against divide by zero, so ignore insanely small moves here
1234 // 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
1235 if(!auxilliary_move && distance < 0.00001F) return false;
1236
1237 if(!auxilliary_move) {
1238 for (size_t i = X_AXIS; i < N_PRIMARY_AXIS; i++) {
1239 // find distance unit vector for primary axis only
1240 unit_vec[i] = deltas[i] / distance;
1241
1242 // Do not move faster than the configured cartesian limits for XYZ
1243 if ( i <= Z_AXIS && max_speeds[i] > 0 ) {
1244 float axis_speed = fabsf(unit_vec[i] * rate_mm_s);
1245
1246 if (axis_speed > max_speeds[i])
1247 rate_mm_s *= ( max_speeds[i] / axis_speed );
1248 }
1249 }
1250
1251 if(this->max_speed > 0 && rate_mm_s > this->max_speed) {
1252 rate_mm_s= this->max_speed;
1253 }
1254 }
1255
1256 // find actuator position given the machine position, use actual adjusted target
1257 ActuatorCoordinates actuator_pos;
1258 if(!disable_arm_solution) {
1259 arm_solution->cartesian_to_actuator( transformed_target, actuator_pos );
1260
1261 }else{
1262 // basically the same as cartesian, would be used for special homing situations like for scara
1263 for (size_t i = X_AXIS; i <= Z_AXIS; i++) {
1264 actuator_pos[i] = transformed_target[i];
1265 }
1266 }
1267
1268 #if MAX_ROBOT_ACTUATORS > 3
1269 sos= 0;
1270 // for the extruders just copy the position, and possibly scale it from mm³ to mm
1271 for (size_t i = E_AXIS; i < n_motors; i++) {
1272 actuator_pos[i]= transformed_target[i];
1273 if(actuators[i]->is_extruder() && get_e_scale_fnc) {
1274 // NOTE this relies on the fact only one extruder is active at a time
1275 // scale for volumetric or flow rate
1276 // TODO is this correct? scaling the absolute target? what if the scale changes?
1277 // for volumetric it basically converts mm³ to mm, but what about flow rate?
1278 actuator_pos[i] *= get_e_scale_fnc();
1279 }
1280 if(auxilliary_move) {
1281 // for E only moves we need to use the scaled E to calculate the distance
1282 sos += powf(actuator_pos[i] - actuators[i]->get_last_milestone(), 2);
1283 }
1284 }
1285 if(auxilliary_move) {
1286 distance= sqrtf(sos); // distance in mm of the e move
1287 if(distance < 0.00001F) return false;
1288 }
1289 #endif
1290
1291 // use default acceleration to start with
1292 float acceleration = default_acceleration;
1293
1294 float isecs = rate_mm_s / distance;
1295
1296 // check per-actuator speed limits
1297 for (size_t actuator = 0; actuator < n_motors; actuator++) {
1298 float d = fabsf(actuator_pos[actuator] - actuators[actuator]->get_last_milestone());
1299 if(d == 0 || !actuators[actuator]->is_selected()) continue; // no movement for this actuator
1300
1301 float actuator_rate= d * isecs;
1302 if (actuator_rate > actuators[actuator]->get_max_rate()) {
1303 rate_mm_s *= (actuators[actuator]->get_max_rate() / actuator_rate);
1304 isecs = rate_mm_s / distance;
1305 }
1306
1307 // adjust acceleration to lowest found, for now just primary axis unless it is an auxiliary move
1308 // TODO we may need to do all of them, check E won't limit XYZ.. it does on long E moves, but not checking it could exceed the E acceleration.
1309 if(auxilliary_move || actuator < N_PRIMARY_AXIS) {
1310 float ma = actuators[actuator]->get_acceleration(); // in mm/sec²
1311 if(!isnan(ma)) { // if axis does not have acceleration set then it uses the default_acceleration
1312 float ca = fabsf((d/distance) * acceleration);
1313 if (ca > ma) {
1314 acceleration *= ( ma / ca );
1315 }
1316 }
1317 }
1318 }
1319
1320 // if we are in feed hold wait here until it is released, this means that even segemnted lines will pause
1321 while(THEKERNEL->get_feed_hold()) {
1322 THEKERNEL->call_event(ON_IDLE, this);
1323 // if we also got a HALT then break out of this
1324 if(THEKERNEL->is_halted()) return false;
1325 }
1326
1327 // Append the block to the planner
1328 // NOTE that distance here should be either the distance travelled by the XYZ axis, or the E mm travel if a solo E move
1329 // NOTE this call will bock until there is room in the block queue, on_idle will continue to be called
1330 if(THEKERNEL->planner->append_block( actuator_pos, n_motors, rate_mm_s, distance, auxilliary_move ? nullptr : unit_vec, acceleration, s_value, is_g123)) {
1331 // this is the new compensated machine position
1332 memcpy(this->compensated_machine_position, transformed_target, n_motors*sizeof(float));
1333 return true;
1334 }
1335
1336 // no actual move
1337 return false;
1338 }
1339
1340 // Used to plan a single move used by things like endstops when homing, zprobe, extruder firmware retracts etc.
1341 bool Robot::delta_move(const float *delta, float rate_mm_s, uint8_t naxis)
1342 {
1343 if(THEKERNEL->is_halted()) return false;
1344
1345 // catch negative or zero feed rates
1346 if(rate_mm_s <= 0.0F) {
1347 return false;
1348 }
1349
1350 // get the absolute target position, default is current machine_position
1351 float target[n_motors];
1352 memcpy(target, machine_position, n_motors*sizeof(float));
1353
1354 // add in the deltas to get new target
1355 for (int i= 0; i < naxis; i++) {
1356 target[i] += delta[i];
1357 }
1358
1359 // submit for planning and if moved update machine_position
1360 if(append_milestone(target, rate_mm_s)) {
1361 memcpy(machine_position, target, n_motors*sizeof(float));
1362 return true;
1363 }
1364
1365 return false;
1366 }
1367
1368 // Append a move to the queue ( cutting it into segments if needed )
1369 bool Robot::append_line(Gcode *gcode, const float target[], float rate_mm_s, float delta_e)
1370 {
1371 // catch negative or zero feed rates and return the same error as GRBL does
1372 if(rate_mm_s <= 0.0F) {
1373 gcode->is_error= true;
1374 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1375 return false;
1376 }
1377
1378 // Find out the distance for this move in XYZ in MCS
1379 float millimeters_of_travel = sqrtf(powf( target[X_AXIS] - machine_position[X_AXIS], 2 ) + powf( target[Y_AXIS] - machine_position[Y_AXIS], 2 ) + powf( target[Z_AXIS] - machine_position[Z_AXIS], 2 ));
1380
1381 if(millimeters_of_travel < 0.00001F) {
1382 // we have no movement in XYZ, probably E only extrude or retract
1383 return this->append_milestone(target, rate_mm_s);
1384 }
1385
1386 /*
1387 For extruders, we need to do some extra work to limit the volumetric rate if specified...
1388 If using volumetric limts we need to be using volumetric extrusion for this to work as Ennn needs to be in mm³ not mm
1389 We ask Extruder to do all the work but we need to pass in the relevant data.
1390 NOTE we need to do this before we segment the line (for deltas)
1391 */
1392 if(!isnan(delta_e) && gcode->has_g && gcode->g == 1) {
1393 float data[2]= {delta_e, rate_mm_s / millimeters_of_travel};
1394 if(PublicData::set_value(extruder_checksum, target_checksum, data)) {
1395 rate_mm_s *= data[1]; // adjust the feedrate
1396 }
1397 }
1398
1399 // 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.
1400 // In delta robots either mm_per_line_segment can be used OR delta_segments_per_second
1401 // 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
1402 uint16_t segments;
1403
1404 if(this->disable_segmentation || (!segment_z_moves && !gcode->has_letter('X') && !gcode->has_letter('Y'))) {
1405 segments= 1;
1406
1407 } else if(this->delta_segments_per_second > 1.0F) {
1408 // enabled if set to something > 1, it is set to 0.0 by default
1409 // segment based on current speed and requested segments per second
1410 // the faster the travel speed the fewer segments needed
1411 // NOTE rate is mm/sec and we take into account any speed override
1412 float seconds = millimeters_of_travel / rate_mm_s;
1413 segments = max(1.0F, ceilf(this->delta_segments_per_second * seconds));
1414 // TODO if we are only moving in Z on a delta we don't really need to segment at all
1415
1416 } else {
1417 if(this->mm_per_line_segment == 0.0F) {
1418 segments = 1; // don't split it up
1419 } else {
1420 segments = ceilf( millimeters_of_travel / this->mm_per_line_segment);
1421 }
1422 }
1423
1424 bool moved= false;
1425 if (segments > 1) {
1426 // A vector to keep track of the endpoint of each segment
1427 float segment_delta[n_motors];
1428 float segment_end[n_motors];
1429 memcpy(segment_end, machine_position, n_motors*sizeof(float));
1430
1431 // How far do we move each segment?
1432 for (int i = 0; i < n_motors; i++)
1433 segment_delta[i] = (target[i] - machine_position[i]) / segments;
1434
1435 // segment 0 is already done - it's the end point of the previous move so we start at segment 1
1436 // We always add another point after this loop so we stop at segments-1, ie i < segments
1437 for (int i = 1; i < segments; i++) {
1438 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
1439 for (int j = 0; j < n_motors; j++)
1440 segment_end[j] += segment_delta[j];
1441
1442 // Append the end of this segment to the queue
1443 // this can block waiting for free block queue or if in feed hold
1444 bool b= this->append_milestone(segment_end, rate_mm_s);
1445 moved= moved || b;
1446 }
1447 }
1448
1449 // Append the end of this full move to the queue
1450 if(this->append_milestone(target, rate_mm_s)) moved= true;
1451
1452 this->next_command_is_MCS = false; // always reset this
1453
1454 return moved;
1455 }
1456
1457
1458 // Append an arc to the queue ( cutting it into segments as needed )
1459 // TODO does not support any E parameters so cannot be used for 3D printing.
1460 bool Robot::append_arc(Gcode * gcode, const float target[], const float offset[], float radius, bool is_clockwise )
1461 {
1462 float rate_mm_s= this->feed_rate / seconds_per_minute;
1463 // catch negative or zero feed rates and return the same error as GRBL does
1464 if(rate_mm_s <= 0.0F) {
1465 gcode->is_error= true;
1466 gcode->txt_after_ok= (rate_mm_s == 0 ? "Undefined feed rate" : "feed rate < 0");
1467 return false;
1468 }
1469
1470 // Scary math.
1471 // We need to use arc_milestone here to get accurate arcs as previous machine_position may have been skipped due to small movements
1472 float center_axis0 = this->arc_milestone[this->plane_axis_0] + offset[this->plane_axis_0];
1473 float center_axis1 = this->arc_milestone[this->plane_axis_1] + offset[this->plane_axis_1];
1474 float linear_travel = target[this->plane_axis_2] - this->arc_milestone[this->plane_axis_2];
1475 float r_axis0 = -offset[this->plane_axis_0]; // Radius vector from center to start position
1476 float r_axis1 = -offset[this->plane_axis_1];
1477 float rt_axis0 = target[this->plane_axis_0] - this->arc_milestone[this->plane_axis_0] - offset[this->plane_axis_0]; // Radius vector from center to target position
1478 float rt_axis1 = target[this->plane_axis_1] - this->arc_milestone[this->plane_axis_1] - offset[this->plane_axis_1];
1479 float angular_travel = 0;
1480 //check for condition where atan2 formula will fail due to everything canceling out exactly
1481 if((this->arc_milestone[this->plane_axis_0]==target[this->plane_axis_0]) && (this->arc_milestone[this->plane_axis_1]==target[this->plane_axis_1])) {
1482 if (is_clockwise) { // set angular_travel to -2pi for a clockwise full circle
1483 angular_travel = (-2 * PI);
1484 } else { // set angular_travel to 2pi for a counterclockwise full circle
1485 angular_travel = (2 * PI);
1486 }
1487 } else {
1488 // Patch from GRBL Firmware - Christoph Baumann 04072015
1489 // CCW angle between position and target from circle center. Only one atan2() trig computation required.
1490 // Only run if not a full circle or angular travel will incorrectly result in 0.0f
1491 angular_travel = atan2f(r_axis0 * rt_axis1 - r_axis1 * rt_axis0, r_axis0 * rt_axis0 + r_axis1 * rt_axis1);
1492 if (plane_axis_2 == Y_AXIS) { is_clockwise = !is_clockwise; } //Math for XZ plane is reverse of other 2 planes
1493 if (is_clockwise) { // adjust angular_travel to be in the range of -2pi to 0 for clockwise arcs
1494 if (angular_travel > 0) { angular_travel -= (2 * PI); }
1495 } else { // adjust angular_travel to be in the range of 0 to 2pi for counterclockwise arcs
1496 if (angular_travel < 0) { angular_travel += (2 * PI); }
1497 }
1498 }
1499
1500 // Find the distance for this gcode
1501 float millimeters_of_travel = hypotf(angular_travel * radius, fabsf(linear_travel));
1502
1503 // We don't care about non-XYZ moves ( for example the extruder produces some of those )
1504 if( millimeters_of_travel < 0.000001F ) {
1505 return false;
1506 }
1507
1508 // limit segments by maximum arc error
1509 float arc_segment = this->mm_per_arc_segment;
1510 if ((this->mm_max_arc_error > 0) && (2 * radius > this->mm_max_arc_error)) {
1511 float min_err_segment = 2 * sqrtf((this->mm_max_arc_error * (2 * radius - this->mm_max_arc_error)));
1512 if (this->mm_per_arc_segment < min_err_segment) {
1513 arc_segment = min_err_segment;
1514 }
1515 }
1516
1517 // catch fall through on above
1518 if(arc_segment < 0.0001F) {
1519 arc_segment= 0.5F; /// the old default, so we avoid the divide by zero
1520 }
1521
1522 // Figure out how many segments for this gcode
1523 // TODO for deltas we need to make sure we are at least as many segments as requested, also if mm_per_line_segment is set we need to use the
1524 uint16_t segments = floorf(millimeters_of_travel / arc_segment);
1525 bool moved= false;
1526
1527 if(segments > 1) {
1528 float theta_per_segment = angular_travel / segments;
1529 float linear_per_segment = linear_travel / segments;
1530
1531 /* Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector,
1532 and phi is the angle of rotation. Based on the solution approach by Jens Geisler.
1533 r_T = [cos(phi) -sin(phi);
1534 sin(phi) cos(phi] * r ;
1535 For arc generation, the center of the circle is the axis of rotation and the radius vector is
1536 defined from the circle center to the initial position. Each line segment is formed by successive
1537 vector rotations. This requires only two cos() and sin() computations to form the rotation
1538 matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since
1539 all float numbers are single precision on the Arduino. (True float precision will not have
1540 round off issues for CNC applications.) Single precision error can accumulate to be greater than
1541 tool precision in some cases. Therefore, arc path correction is implemented.
1542
1543 Small angle approximation may be used to reduce computation overhead further. This approximation
1544 holds for everything, but very small circles and large mm_per_arc_segment values. In other words,
1545 theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large
1546 to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for
1547 numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an
1548 issue for CNC machines with the single precision Arduino calculations.
1549 This approximation also allows mc_arc to immediately insert a line segment into the planner
1550 without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied
1551 a correction, the planner should have caught up to the lag caused by the initial mc_arc overhead.
1552 This is important when there are successive arc motions.
1553 */
1554 // Vector rotation matrix values
1555 float cos_T = 1 - 0.5F * theta_per_segment * theta_per_segment; // Small angle approximation
1556 float sin_T = theta_per_segment;
1557
1558 // TODO we need to handle the ABC axis here by segmenting them
1559 float arc_target[n_motors];
1560 float sin_Ti;
1561 float cos_Ti;
1562 float r_axisi;
1563 uint16_t i;
1564 int8_t count = 0;
1565
1566 // init array for all axis
1567 memcpy(arc_target, machine_position, n_motors*sizeof(float));
1568
1569 // Initialize the linear axis
1570 arc_target[this->plane_axis_2] = this->machine_position[this->plane_axis_2];
1571
1572 for (i = 1; i < segments; i++) { // Increment (segments-1)
1573 if(THEKERNEL->is_halted()) return false; // don't queue any more segments
1574
1575 if (count < this->arc_correction ) {
1576 // Apply vector rotation matrix
1577 r_axisi = r_axis0 * sin_T + r_axis1 * cos_T;
1578 r_axis0 = r_axis0 * cos_T - r_axis1 * sin_T;
1579 r_axis1 = r_axisi;
1580 count++;
1581 } else {
1582 // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
1583 // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
1584 cos_Ti = cosf(i * theta_per_segment);
1585 sin_Ti = sinf(i * theta_per_segment);
1586 r_axis0 = -offset[this->plane_axis_0] * cos_Ti + offset[this->plane_axis_1] * sin_Ti;
1587 r_axis1 = -offset[this->plane_axis_0] * sin_Ti - offset[this->plane_axis_1] * cos_Ti;
1588 count = 0;
1589 }
1590
1591 // Update arc_target location
1592 arc_target[this->plane_axis_0] = center_axis0 + r_axis0;
1593 arc_target[this->plane_axis_1] = center_axis1 + r_axis1;
1594 arc_target[this->plane_axis_2] += linear_per_segment;
1595
1596 // Append this segment to the queue
1597 bool b= this->append_milestone(arc_target, rate_mm_s);
1598 moved= moved || b;
1599 }
1600 }
1601
1602 // Ensure last segment arrives at target location.
1603 if(this->append_milestone(target, rate_mm_s)) moved= true;
1604
1605 return moved;
1606 }
1607
1608 // Do the math for an arc and add it to the queue
1609 bool Robot::compute_arc(Gcode * gcode, const float offset[], const float target[], enum MOTION_MODE_T motion_mode)
1610 {
1611
1612 // Find the radius
1613 float radius = hypotf(offset[this->plane_axis_0], offset[this->plane_axis_1]);
1614
1615 // Set clockwise/counter-clockwise sign for mc_arc computations
1616 bool is_clockwise = false;
1617 if( motion_mode == CW_ARC ) {
1618 is_clockwise = true;
1619 }
1620
1621 // Append arc
1622 return this->append_arc(gcode, target, offset, radius, is_clockwise );
1623 }
1624
1625
1626 float Robot::theta(float x, float y)
1627 {
1628 float t = atanf(x / fabs(y));
1629 if (y > 0) {
1630 return(t);
1631 } else {
1632 if (t > 0) {
1633 return(PI - t);
1634 } else {
1635 return(-PI - t);
1636 }
1637 }
1638 }
1639
1640 void Robot::select_plane(uint8_t axis_0, uint8_t axis_1, uint8_t axis_2)
1641 {
1642 this->plane_axis_0 = axis_0;
1643 this->plane_axis_1 = axis_1;
1644 this->plane_axis_2 = axis_2;
1645 }
1646
1647 void Robot::clearToolOffset()
1648 {
1649 this->tool_offset= wcs_t(0,0,0);
1650 }
1651
1652 void Robot::setToolOffset(const float offset[3])
1653 {
1654 this->tool_offset= wcs_t(offset[0], offset[1], offset[2]);
1655 }
1656
1657 float Robot::get_feed_rate() const
1658 {
1659 return THEKERNEL->gcode_dispatch->get_modal_command() == 0 ? seek_rate : feed_rate;
1660 }
1661
1662 bool Robot::is_homed(uint8_t i) const
1663 {
1664 if(i >= 3) return false; // safety
1665
1666 // if we are homing we ignore soft endstops so return false
1667 bool homing;
1668 bool ok = PublicData::get_value(endstops_checksum, get_homing_status_checksum, 0, &homing);
1669 if(!ok || homing) return false;
1670
1671 // check individual axis homing status
1672 bool homed[3];
1673 ok = PublicData::get_value(endstops_checksum, get_homed_status_checksum, 0, homed);
1674 if(!ok) return false;
1675 return homed[i];
1676 }