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